VC UI: Add return to app panel UI
Given the information from VideoConferenceTrayController, renders a ReturnToAppPanel that displays the information of currently running media apps. This CL: - Handles the data flow from the controller to the panel UI - Defines all children classes and layouts of the panel, which shows all the apps information and the aggregated summary at the top. Screenshot: - https://screenshot.googleplex.com/mi95mpsE7mx7Vn2 - https://screenshot.googleplex.com/8ZiB9bsd4Zkt3DA Bug: b:253272945 Change-Id: Idf40f2f3b41b2e1e761a2db375758dc7a5bc5292 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4061999 Reviewed-by: Alex Newcomer <newcomer@chromium.org> Reviewed-by: Ahmed Fakhry <afakhry@chromium.org> Commit-Queue: Andre Le <leandre@chromium.org> Cr-Commit-Position: refs/heads/main@{#1087154}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
303fd02c2e
commit
2dd64a9997
@ -1953,8 +1953,8 @@ component("ash") {
|
|||||||
"system/video_conference/bubble/bubble_view.cc",
|
"system/video_conference/bubble/bubble_view.cc",
|
||||||
"system/video_conference/bubble/bubble_view.h",
|
"system/video_conference/bubble/bubble_view.h",
|
||||||
"system/video_conference/bubble/bubble_view_ids.h",
|
"system/video_conference/bubble/bubble_view_ids.h",
|
||||||
"system/video_conference/bubble/return_to_app_button.cc",
|
"system/video_conference/bubble/return_to_app_panel.cc",
|
||||||
"system/video_conference/bubble/return_to_app_button.h",
|
"system/video_conference/bubble/return_to_app_panel.h",
|
||||||
"system/video_conference/bubble/set_value_effects_view.cc",
|
"system/video_conference/bubble/set_value_effects_view.cc",
|
||||||
"system/video_conference/bubble/set_value_effects_view.h",
|
"system/video_conference/bubble/set_value_effects_view.h",
|
||||||
"system/video_conference/bubble/toggle_effects_view.cc",
|
"system/video_conference/bubble/toggle_effects_view.cc",
|
||||||
@ -3217,6 +3217,7 @@ test("ash_unittests") {
|
|||||||
"system/update/update_notification_controller_unittest.cc",
|
"system/update/update_notification_controller_unittest.cc",
|
||||||
"system/usb_peripheral/usb_peripheral_notification_controller_unittest.cc",
|
"system/usb_peripheral/usb_peripheral_notification_controller_unittest.cc",
|
||||||
"system/video_conference/bubble/bubble_view_unittest.cc",
|
"system/video_conference/bubble/bubble_view_unittest.cc",
|
||||||
|
"system/video_conference/bubble/return_to_app_panel_unittest.cc",
|
||||||
"system/video_conference/effects/video_conference_tray_effects_manager_unittest.cc",
|
"system/video_conference/effects/video_conference_tray_effects_manager_unittest.cc",
|
||||||
"system/video_conference/video_conference_tray_unittest.cc",
|
"system/video_conference/video_conference_tray_unittest.cc",
|
||||||
"system/virtual_keyboard/virtual_keyboard_tray_unittest.cc",
|
"system/virtual_keyboard/virtual_keyboard_tray_unittest.cc",
|
||||||
|
@ -1448,6 +1448,9 @@ Style notes:
|
|||||||
<message name="IDS_ASH_VIDEO_CONFERENCE_TOGGLE_BUBBLE_BUTTON_TOOLTIP" desc="The tooltip for the toggle bubble button in the video conference tray.">
|
<message name="IDS_ASH_VIDEO_CONFERENCE_TOGGLE_BUBBLE_BUTTON_TOOLTIP" desc="The tooltip for the toggle bubble button in the video conference tray.">
|
||||||
Camera and audio controls
|
Camera and audio controls
|
||||||
</message>
|
</message>
|
||||||
|
<message name="IDS_ASH_VIDEO_CONFERENCE_RETURN_TO_APP_SUMMARY_TEXT" desc="The summary text in the return to app panel of the video conference panel, specifying how many video conferencing apps are in used.">
|
||||||
|
Used by <ph name="APP_COUNT">$1<ex>2</ex></ph> apps
|
||||||
|
</message>
|
||||||
|
|
||||||
<!-- Phone Hub tray-->
|
<!-- Phone Hub tray-->
|
||||||
<message name="IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME" desc="The accessible name of the Phone Hub tray bubble for screen readers.">
|
<message name="IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME" desc="The accessible name of the Phone Hub tray bubble for screen readers.">
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
9c5553d20f01fb19fb379a165d5a9f1368505370
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "ash/system/tray/tray_bubble_view.h"
|
#include "ash/system/tray/tray_bubble_view.h"
|
||||||
#include "ash/system/video_conference/bubble/bubble_view_ids.h"
|
#include "ash/system/video_conference/bubble/bubble_view_ids.h"
|
||||||
#include "ash/system/video_conference/bubble/return_to_app_button.h"
|
#include "ash/system/video_conference/bubble/return_to_app_panel.h"
|
||||||
#include "ash/system/video_conference/bubble/set_value_effects_view.h"
|
#include "ash/system/video_conference/bubble/set_value_effects_view.h"
|
||||||
#include "ash/system/video_conference/bubble/toggle_effects_view.h"
|
#include "ash/system/video_conference/bubble/toggle_effects_view.h"
|
||||||
#include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
|
#include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
|
||||||
@ -37,10 +37,10 @@ BubbleView::BubbleView(const InitParams& init_params,
|
|||||||
layout->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
|
layout->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
|
||||||
layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
|
layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
|
||||||
|
|
||||||
// `ReturnToAppButton` resides in the top-level layout and isn't part of the
|
// `ReturnToAppPanel` resides in the top-level layout and isn't part of the
|
||||||
// scrollable area (that can't be added until the `BubbleView` officially has
|
// scrollable area (that can't be added until the `BubbleView` officially has
|
||||||
// a parent waidget).
|
// a parent waidget).
|
||||||
AddChildView(std::make_unique<ReturnToAppButton>());
|
AddChildView(std::make_unique<ReturnToAppPanel>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BubbleView::AddedToWidget() {
|
void BubbleView::AddedToWidget() {
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
// Copyright 2022 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/system/video_conference/bubble/return_to_app_button.h"
|
|
||||||
|
|
||||||
#include "ash/resources/vector_icons/vector_icons.h"
|
|
||||||
#include "ash/style/ash_color_id.h"
|
|
||||||
#include "ash/system/video_conference/bubble/bubble_view_ids.h"
|
|
||||||
#include "ui/base/models/image_model.h"
|
|
||||||
#include "ui/views/background.h"
|
|
||||||
#include "ui/views/border.h"
|
|
||||||
#include "ui/views/controls/image_view.h"
|
|
||||||
#include "ui/views/controls/label.h"
|
|
||||||
#include "ui/views/layout/flex_layout.h"
|
|
||||||
|
|
||||||
namespace ash::video_conference {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
const int kBorderInsetDimension = 10;
|
|
||||||
const int kBackgroundRoundedRectRadius = 10;
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// TODO(b/253274599, b/253274147, b/253272945) Implement actual "return to app"
|
|
||||||
// button functionality. This is a temporary placeholder for facilitating VC
|
|
||||||
// bubble development.
|
|
||||||
ReturnToAppButton::ReturnToAppButton() {
|
|
||||||
SetID(BubbleViewID::kReturnToApp);
|
|
||||||
|
|
||||||
views::FlexLayout* layout =
|
|
||||||
SetLayoutManager(std::make_unique<views::FlexLayout>());
|
|
||||||
layout->SetOrientation(views::LayoutOrientation::kHorizontal);
|
|
||||||
layout->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
|
|
||||||
layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
|
|
||||||
|
|
||||||
auto camera = std::make_unique<views::ImageView>();
|
|
||||||
camera->SetImage(ui::ImageModel::FromVectorIcon(kPrivacyIndicatorsCameraIcon,
|
|
||||||
kColorAshIconColorPrimary));
|
|
||||||
AddChildView(std::move(camera));
|
|
||||||
|
|
||||||
auto microphone = std::make_unique<views::ImageView>();
|
|
||||||
microphone->SetImage(ui::ImageModel::FromVectorIcon(
|
|
||||||
kPrivacyIndicatorsMicrophoneIcon, kColorAshIconColorPrimary));
|
|
||||||
AddChildView(std::move(microphone));
|
|
||||||
|
|
||||||
AddChildView(std::make_unique<views::Label>(u"Meet"));
|
|
||||||
|
|
||||||
SetBorder(views::CreateEmptyBorder(
|
|
||||||
gfx::Insets::VH(kBorderInsetDimension, kBorderInsetDimension)));
|
|
||||||
SetBackground(views::CreateRoundedRectBackground(
|
|
||||||
gfx::kGoogleBlue800, kBackgroundRoundedRectRadius));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ash::video_conference
|
|
@ -1,26 +0,0 @@
|
|||||||
// Copyright 2022 The Chromium Authors
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_RETURN_TO_APP_BUTTON_H_
|
|
||||||
#define ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_RETURN_TO_APP_BUTTON_H_
|
|
||||||
|
|
||||||
#include "ui/views/view.h"
|
|
||||||
|
|
||||||
namespace ash::video_conference {
|
|
||||||
|
|
||||||
// The "return to app" button that resides in the video conference bubble. The
|
|
||||||
// user selects from a list of apps that are actively capturing audio/video
|
|
||||||
// and/or sharing the screen, and the selected app is brought to the top and
|
|
||||||
// focused.
|
|
||||||
class ReturnToAppButton : public views::View {
|
|
||||||
public:
|
|
||||||
ReturnToAppButton();
|
|
||||||
ReturnToAppButton(const ReturnToAppButton&) = delete;
|
|
||||||
ReturnToAppButton& operator=(const ReturnToAppButton&) = delete;
|
|
||||||
~ReturnToAppButton() override = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ash::video_conference
|
|
||||||
|
|
||||||
#endif // ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_RETURN_TO_APP_BUTTON_H_
|
|
171
ash/system/video_conference/bubble/return_to_app_panel.cc
Normal file
171
ash/system/video_conference/bubble/return_to_app_panel.cc
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// Copyright 2022 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/system/video_conference/bubble/return_to_app_panel.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "ash/resources/vector_icons/vector_icons.h"
|
||||||
|
#include "ash/strings/grit/ash_strings.h"
|
||||||
|
#include "ash/style/ash_color_id.h"
|
||||||
|
#include "ash/system/video_conference/bubble/bubble_view_ids.h"
|
||||||
|
#include "ash/system/video_conference/video_conference_tray_controller.h"
|
||||||
|
#include "base/strings/utf_string_conversions.h"
|
||||||
|
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
|
||||||
|
#include "ui/base/l10n/l10n_util.h"
|
||||||
|
#include "ui/base/models/image_model.h"
|
||||||
|
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
|
||||||
|
#include "ui/views/background.h"
|
||||||
|
#include "ui/views/border.h"
|
||||||
|
#include "ui/views/controls/image_view.h"
|
||||||
|
#include "ui/views/controls/label.h"
|
||||||
|
#include "ui/views/layout/flex_layout.h"
|
||||||
|
|
||||||
|
namespace ash::video_conference {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const int kReturnToAppPanelRadius = 16;
|
||||||
|
const int kReturnToAppPanelSpacing = 8;
|
||||||
|
const int kReturnToAppButtonSpacing = 12;
|
||||||
|
const int kReturnToAppButtonIconsSpacing = 2;
|
||||||
|
|
||||||
|
// Creates a view containing camera, microphone, and screen share icons that
|
||||||
|
// shows capturing state of a media app.
|
||||||
|
std::unique_ptr<views::View> CreateReturnToAppIconsContainer(
|
||||||
|
bool is_capturing_camera,
|
||||||
|
bool is_capturing_microphone,
|
||||||
|
bool is_capturing_screen) {
|
||||||
|
auto container = std::make_unique<views::View>();
|
||||||
|
container->SetLayoutManager(std::make_unique<views::FlexLayout>())
|
||||||
|
->SetOrientation(views::LayoutOrientation::kHorizontal)
|
||||||
|
.SetMainAxisAlignment(views::LayoutAlignment::kCenter)
|
||||||
|
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
|
||||||
|
.SetDefault(views::kMarginsKey,
|
||||||
|
gfx::Insets::TLBR(0, kReturnToAppButtonIconsSpacing / 2, 0,
|
||||||
|
kReturnToAppButtonIconsSpacing / 2));
|
||||||
|
|
||||||
|
if (is_capturing_camera) {
|
||||||
|
auto camera_icon = std::make_unique<views::ImageView>();
|
||||||
|
camera_icon->SetImage(ui::ImageModel::FromVectorIcon(
|
||||||
|
kPrivacyIndicatorsCameraIcon, cros_tokens::kCrosSysPositive));
|
||||||
|
container->AddChildView(std::move(camera_icon));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_capturing_microphone) {
|
||||||
|
auto microphone_icon = std::make_unique<views::ImageView>();
|
||||||
|
microphone_icon->SetImage(ui::ImageModel::FromVectorIcon(
|
||||||
|
kPrivacyIndicatorsMicrophoneIcon, cros_tokens::kCrosSysPositive));
|
||||||
|
container->AddChildView(std::move(microphone_icon));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_capturing_screen) {
|
||||||
|
auto screen_share_icon = std::make_unique<views::ImageView>();
|
||||||
|
screen_share_icon->SetImage(ui::ImageModel::FromVectorIcon(
|
||||||
|
kPrivacyIndicatorsScreenShareIcon, cros_tokens::kCrosSysPositive));
|
||||||
|
container->AddChildView(std::move(screen_share_icon));
|
||||||
|
}
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the display text representing a media app shown in the return to app
|
||||||
|
// panel.
|
||||||
|
std::u16string GetMediaAppDisplayText(
|
||||||
|
mojo::StructPtr<crosapi::mojom::VideoConferenceMediaAppInfo>& media_app) {
|
||||||
|
// Displays the url if it is valid. Otherwise, display app title.
|
||||||
|
auto url = media_app->url;
|
||||||
|
return url && url->is_valid() ? base::UTF8ToUTF16(url->GetContent())
|
||||||
|
: media_app->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// ReturnToAppButton:
|
||||||
|
|
||||||
|
ReturnToAppButton::ReturnToAppButton(bool is_capturing_camera,
|
||||||
|
bool is_capturing_microphone,
|
||||||
|
bool is_capturing_screen,
|
||||||
|
const std::u16string& display_text)
|
||||||
|
: is_capturing_camera_(is_capturing_camera),
|
||||||
|
is_capturing_microphone_(is_capturing_microphone),
|
||||||
|
is_capturing_screen_(is_capturing_screen) {
|
||||||
|
SetLayoutManager(std::make_unique<views::FlexLayout>())
|
||||||
|
->SetOrientation(views::LayoutOrientation::kHorizontal)
|
||||||
|
.SetMainAxisAlignment(views::LayoutAlignment::kCenter)
|
||||||
|
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
|
||||||
|
.SetDefault(views::kMarginsKey,
|
||||||
|
gfx::Insets::TLBR(0, kReturnToAppButtonSpacing / 2, 0,
|
||||||
|
kReturnToAppButtonSpacing / 2));
|
||||||
|
|
||||||
|
AddChildView(CreateReturnToAppIconsContainer(
|
||||||
|
is_capturing_camera, is_capturing_microphone, is_capturing_screen));
|
||||||
|
|
||||||
|
label_ = AddChildView(std::make_unique<views::Label>(display_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// ReturnToAppPanel:
|
||||||
|
|
||||||
|
ReturnToAppPanel::ReturnToAppPanel() {
|
||||||
|
SetID(BubbleViewID::kReturnToApp);
|
||||||
|
|
||||||
|
SetLayoutManager(std::make_unique<views::FlexLayout>())
|
||||||
|
->SetOrientation(views::LayoutOrientation::kVertical)
|
||||||
|
.SetMainAxisAlignment(views::LayoutAlignment::kCenter)
|
||||||
|
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
|
||||||
|
.SetDefault(views::kMarginsKey,
|
||||||
|
gfx::Insets::TLBR(0, 0, kReturnToAppPanelSpacing, 0))
|
||||||
|
.SetInteriorMargin(gfx::Insets::TLBR(12, 0, 0, 0));
|
||||||
|
|
||||||
|
// Add running media apps buttons to the panel.
|
||||||
|
VideoConferenceTrayController::Get()->GetMediaApps(base::BindOnce(
|
||||||
|
&ReturnToAppPanel::AddButtonsToPanel, weak_ptr_factory_.GetWeakPtr()));
|
||||||
|
|
||||||
|
SetBackground(views::CreateThemedRoundedRectBackground(
|
||||||
|
cros_tokens::kCrosSysSystemOnBase, kReturnToAppPanelRadius));
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnToAppPanel::~ReturnToAppPanel() = default;
|
||||||
|
|
||||||
|
void ReturnToAppPanel::AddButtonsToPanel(MediaApps apps) {
|
||||||
|
if (apps.size() < 1) {
|
||||||
|
SetVisible(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apps.size() == 1) {
|
||||||
|
auto& app = apps.front();
|
||||||
|
AddChildView(std::make_unique<ReturnToAppButton>(
|
||||||
|
app->is_capturing_camera, app->is_capturing_microphone,
|
||||||
|
app->is_capturing_screen, GetMediaAppDisplayText(app)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool any_apps_capturing_camera = false;
|
||||||
|
bool any_apps_capturing_microphone = false;
|
||||||
|
bool any_apps_capturing_screen = false;
|
||||||
|
|
||||||
|
for (auto& app : apps) {
|
||||||
|
AddChildView(std::make_unique<ReturnToAppButton>(
|
||||||
|
app->is_capturing_camera, app->is_capturing_microphone,
|
||||||
|
app->is_capturing_screen, GetMediaAppDisplayText(app)));
|
||||||
|
|
||||||
|
any_apps_capturing_camera |= app->is_capturing_camera;
|
||||||
|
any_apps_capturing_microphone |= app->is_capturing_microphone;
|
||||||
|
any_apps_capturing_screen |= app->is_capturing_screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto summary_text = l10n_util::GetStringFUTF16Int(
|
||||||
|
IDS_ASH_VIDEO_CONFERENCE_RETURN_TO_APP_SUMMARY_TEXT,
|
||||||
|
static_cast<int>(apps.size()));
|
||||||
|
|
||||||
|
AddChildViewAt(std::make_unique<ReturnToAppButton>(
|
||||||
|
any_apps_capturing_camera, any_apps_capturing_microphone,
|
||||||
|
any_apps_capturing_screen, summary_text),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ash::video_conference
|
76
ash/system/video_conference/bubble/return_to_app_panel.h
Normal file
76
ash/system/video_conference/bubble/return_to_app_panel.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2022 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_RETURN_TO_APP_PANEL_H_
|
||||||
|
#define ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_RETURN_TO_APP_PANEL_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "ash/ash_export.h"
|
||||||
|
#include "base/memory/weak_ptr.h"
|
||||||
|
#include "chromeos/crosapi/mojom/video_conference.mojom-forward.h"
|
||||||
|
#include "ui/views/view.h"
|
||||||
|
|
||||||
|
namespace views {
|
||||||
|
class Label;
|
||||||
|
} // namespace views
|
||||||
|
|
||||||
|
namespace ash::video_conference {
|
||||||
|
|
||||||
|
using MediaApps = std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr>;
|
||||||
|
|
||||||
|
// The "return to app" button that resides within the "return to app" panel,
|
||||||
|
// showing information of a particular running media app. Clicking on this
|
||||||
|
// button will take users to the app.
|
||||||
|
class ASH_EXPORT ReturnToAppButton : public views::View {
|
||||||
|
public:
|
||||||
|
ReturnToAppButton(bool is_capturing_camera,
|
||||||
|
bool is_capturing_microphone,
|
||||||
|
bool is_capturing_screen,
|
||||||
|
const std::u16string& display_text);
|
||||||
|
|
||||||
|
ReturnToAppButton(const ReturnToAppButton&) = delete;
|
||||||
|
ReturnToAppButton& operator=(const ReturnToAppButton&) = delete;
|
||||||
|
|
||||||
|
~ReturnToAppButton() override = default;
|
||||||
|
|
||||||
|
bool is_capturing_camera() const { return is_capturing_camera_; }
|
||||||
|
bool is_capturing_microphone() const { return is_capturing_microphone_; }
|
||||||
|
bool is_capturing_screen() const { return is_capturing_screen_; }
|
||||||
|
|
||||||
|
views::Label* label() { return label_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Indicates if the running app is using camera, microphone, or screen
|
||||||
|
// sharing.
|
||||||
|
const bool is_capturing_camera_;
|
||||||
|
const bool is_capturing_microphone_;
|
||||||
|
const bool is_capturing_screen_;
|
||||||
|
|
||||||
|
// Label showing the url or name of the running app. Owned by the views
|
||||||
|
// hierarchy.
|
||||||
|
views::Label* label_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The "return to app" panel that resides in the video conference bubble. The
|
||||||
|
// user selects from a list of apps that are actively capturing audio/video
|
||||||
|
// and/or sharing the screen, and the selected app is brought to the top and
|
||||||
|
// focused.
|
||||||
|
class ASH_EXPORT ReturnToAppPanel : public views::View {
|
||||||
|
public:
|
||||||
|
ReturnToAppPanel();
|
||||||
|
ReturnToAppPanel(const ReturnToAppPanel&) = delete;
|
||||||
|
ReturnToAppPanel& operator=(const ReturnToAppPanel&) = delete;
|
||||||
|
~ReturnToAppPanel() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Used by the ctor to add `ReturnToAppButton`(s) to the panel.
|
||||||
|
void AddButtonsToPanel(MediaApps apps);
|
||||||
|
|
||||||
|
base::WeakPtrFactory<ReturnToAppPanel> weak_ptr_factory_{this};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ash::video_conference
|
||||||
|
|
||||||
|
#endif // ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_RETURN_TO_APP_PANEL_H_
|
@ -0,0 +1,158 @@
|
|||||||
|
// Copyright 2022 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/system/video_conference/bubble/return_to_app_panel.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "ash/constants/ash_features.h"
|
||||||
|
#include "ash/strings/grit/ash_strings.h"
|
||||||
|
#include "ash/system/video_conference/fake_video_conference_tray_controller.h"
|
||||||
|
#include "ash/test/ash_test_base.h"
|
||||||
|
#include "base/test/scoped_feature_list.h"
|
||||||
|
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
|
||||||
|
#include "ui/base/l10n/l10n_util.h"
|
||||||
|
#include "ui/views/controls/label.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Verifies the information of `ReturnToAppButton`.
|
||||||
|
void VerifyReturnToAppButtonInfo(
|
||||||
|
ash::video_conference::ReturnToAppButton* button,
|
||||||
|
bool is_capturing_camera,
|
||||||
|
bool is_capturing_microphone,
|
||||||
|
bool is_capturing_screen,
|
||||||
|
const std::u16string& display_text) {
|
||||||
|
EXPECT_EQ(is_capturing_camera, button->is_capturing_camera());
|
||||||
|
EXPECT_EQ(is_capturing_microphone, button->is_capturing_microphone());
|
||||||
|
EXPECT_EQ(is_capturing_screen, button->is_capturing_screen());
|
||||||
|
EXPECT_EQ(display_text, button->label()->GetText());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for verifying displayed url.
|
||||||
|
const std::string kGoogleMeetTestUrl = "https://meet.google.com/abc-xyz/ab-123";
|
||||||
|
const std::u16string kExpectedGoogleMeetDisplayedUrl =
|
||||||
|
u"meet.google.com/abc-xyz/ab-123";
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace ash::video_conference {
|
||||||
|
|
||||||
|
class ReturnToAppPanelTest : public AshTestBase {
|
||||||
|
public:
|
||||||
|
ReturnToAppPanelTest() = default;
|
||||||
|
ReturnToAppPanelTest(const ReturnToAppPanelTest&) = delete;
|
||||||
|
ReturnToAppPanelTest& operator=(const ReturnToAppPanelTest&) = delete;
|
||||||
|
~ReturnToAppPanelTest() override = default;
|
||||||
|
|
||||||
|
// AshTestBase:
|
||||||
|
void SetUp() override {
|
||||||
|
scoped_feature_list_.InitAndEnableFeature(features::kVcControlsUi);
|
||||||
|
|
||||||
|
// Here we have to create the global instance of `CrasAudioHandler` before
|
||||||
|
// `FakeVideoConferenceTrayController`, so we do it here and not do it in
|
||||||
|
// `AshTestBase`.
|
||||||
|
CrasAudioClient::InitializeFake();
|
||||||
|
CrasAudioHandler::InitializeForTesting();
|
||||||
|
|
||||||
|
// Instantiates a fake controller (the real one is created in
|
||||||
|
// ChromeBrowserMainExtraPartsAsh::PreProfileInit() which is not called in
|
||||||
|
// ash unit tests).
|
||||||
|
controller_ = std::make_unique<FakeVideoConferenceTrayController>();
|
||||||
|
|
||||||
|
set_create_global_cras_audio_handler(false);
|
||||||
|
AshTestBase::SetUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
AshTestBase::TearDown();
|
||||||
|
controller_.reset();
|
||||||
|
CrasAudioHandler::Shutdown();
|
||||||
|
CrasAudioClient::Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
FakeVideoConferenceTrayController* controller() { return controller_.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
base::test::ScopedFeatureList scoped_feature_list_;
|
||||||
|
std::unique_ptr<FakeVideoConferenceTrayController> controller_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(ReturnToAppPanelTest, NoApp) {
|
||||||
|
controller()->ClearMediaApps();
|
||||||
|
|
||||||
|
// The view should not be visible when there's no app.
|
||||||
|
auto return_to_app_panel = std::make_unique<ReturnToAppPanel>();
|
||||||
|
EXPECT_FALSE(return_to_app_panel->GetVisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ReturnToAppPanelTest, OneApp) {
|
||||||
|
bool is_capturing_camera = true;
|
||||||
|
bool is_capturing_microphone = false;
|
||||||
|
bool is_capturing_screen = false;
|
||||||
|
controller()->ClearMediaApps();
|
||||||
|
controller()->AddMediaApp(crosapi::mojom::VideoConferenceMediaAppInfo::New(
|
||||||
|
/*id=*/base::UnguessableToken::Create(),
|
||||||
|
/*last_activity_time=*/base::Time::Now(), is_capturing_camera,
|
||||||
|
is_capturing_microphone, is_capturing_screen, /*title=*/u"Google Meet",
|
||||||
|
/*url=*/GURL(kGoogleMeetTestUrl)));
|
||||||
|
|
||||||
|
// There should be one child representing the only one running media app.
|
||||||
|
auto return_to_app_panel = std::make_unique<ReturnToAppPanel>();
|
||||||
|
EXPECT_EQ(1u, return_to_app_panel->children().size());
|
||||||
|
|
||||||
|
auto* app_button =
|
||||||
|
static_cast<ReturnToAppButton*>(return_to_app_panel->children().front());
|
||||||
|
VerifyReturnToAppButtonInfo(app_button, is_capturing_camera,
|
||||||
|
is_capturing_microphone, is_capturing_screen,
|
||||||
|
kExpectedGoogleMeetDisplayedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ReturnToAppPanelTest, MultipleApps) {
|
||||||
|
controller()->ClearMediaApps();
|
||||||
|
controller()->AddMediaApp(crosapi::mojom::VideoConferenceMediaAppInfo::New(
|
||||||
|
/*id=*/base::UnguessableToken::Create(),
|
||||||
|
/*last_activity_time=*/base::Time::Now(),
|
||||||
|
/*is_capturing_camera=*/true, /*is_capturing_microphone=*/false,
|
||||||
|
/*is_capturing_screen=*/false, /*title=*/u"Google Meet",
|
||||||
|
/*url=*/GURL(kGoogleMeetTestUrl)));
|
||||||
|
controller()->AddMediaApp(crosapi::mojom::VideoConferenceMediaAppInfo::New(
|
||||||
|
/*id=*/base::UnguessableToken::Create(),
|
||||||
|
/*last_activity_time=*/base::Time::Now(),
|
||||||
|
/*is_capturing_camera=*/false, /*is_capturing_microphone=*/true,
|
||||||
|
/*is_capturing_screen=*/true, /*title=*/u"Zoom",
|
||||||
|
/*url=*/absl::nullopt));
|
||||||
|
|
||||||
|
// There should be three children, one representing the summary row and two
|
||||||
|
// for two running media apps.
|
||||||
|
auto return_to_app_panel = std::make_unique<ReturnToAppPanel>();
|
||||||
|
EXPECT_EQ(3u, return_to_app_panel->children().size());
|
||||||
|
|
||||||
|
// The first row should be the summary row, representing the state of
|
||||||
|
// capturing from all apps and showing that 2 apps are running.
|
||||||
|
auto* summary_row =
|
||||||
|
static_cast<ReturnToAppButton*>(return_to_app_panel->children().front());
|
||||||
|
VerifyReturnToAppButtonInfo(
|
||||||
|
summary_row, /*is_capturing_camera=*/true,
|
||||||
|
/*is_capturing_microphone=*/true,
|
||||||
|
/*is_capturing_screen=*/true,
|
||||||
|
l10n_util::GetStringFUTF16Int(
|
||||||
|
IDS_ASH_VIDEO_CONFERENCE_RETURN_TO_APP_SUMMARY_TEXT, 2));
|
||||||
|
|
||||||
|
// Verify the next 2 rows, representing the 2 running apps.
|
||||||
|
auto* first_app_row =
|
||||||
|
static_cast<ReturnToAppButton*>(return_to_app_panel->children()[1]);
|
||||||
|
VerifyReturnToAppButtonInfo(first_app_row, /*is_capturing_camera=*/true,
|
||||||
|
/*is_capturing_microphone=*/false,
|
||||||
|
/*is_capturing_screen=*/false,
|
||||||
|
kExpectedGoogleMeetDisplayedUrl);
|
||||||
|
|
||||||
|
// If the url is not provided, the button should display the app title.
|
||||||
|
auto* second_app_row =
|
||||||
|
static_cast<ReturnToAppButton*>(return_to_app_panel->children()[2]);
|
||||||
|
VerifyReturnToAppButtonInfo(second_app_row, /*is_capturing_camera=*/false,
|
||||||
|
/*is_capturing_microphone=*/true,
|
||||||
|
/*is_capturing_screen=*/true, u"Zoom");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ash::video_conference
|
@ -5,15 +5,32 @@
|
|||||||
#include "ash/system/video_conference/fake_video_conference_tray_controller.h"
|
#include "ash/system/video_conference/fake_video_conference_tray_controller.h"
|
||||||
|
|
||||||
#include "ash/system/video_conference/effects/fake_video_conference_effects.h"
|
#include "ash/system/video_conference/effects/fake_video_conference_effects.h"
|
||||||
|
#include "ash/system/video_conference/video_conference_tray_controller.h"
|
||||||
|
#include "base/functional/callback.h"
|
||||||
#include "chromeos/ash/components/audio/cras_audio_handler.h"
|
#include "chromeos/ash/components/audio/cras_audio_handler.h"
|
||||||
|
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
|
||||||
#include "media/capture/video/chromeos/mojom/cros_camera_service.mojom-shared.h"
|
#include "media/capture/video/chromeos/mojom/cros_camera_service.mojom-shared.h"
|
||||||
|
#include "url/gurl.h"
|
||||||
|
|
||||||
namespace ash {
|
namespace ash {
|
||||||
|
|
||||||
FakeVideoConferenceTrayController::FakeVideoConferenceTrayController()
|
FakeVideoConferenceTrayController::FakeVideoConferenceTrayController()
|
||||||
: effect_repository_(
|
: effect_repository_(
|
||||||
std::make_unique<fake_video_conference::EffectRepository>(
|
std::make_unique<fake_video_conference::EffectRepository>(
|
||||||
/*controller=*/this)) {}
|
/*controller=*/this)) {
|
||||||
|
AddMediaApp(crosapi::mojom::VideoConferenceMediaAppInfo::New(
|
||||||
|
/*id=*/base::UnguessableToken::Create(),
|
||||||
|
/*last_activity_time=*/base::Time::Now(),
|
||||||
|
/*is_capturing_camera=*/true, /*is_capturing_microphone=*/false,
|
||||||
|
/*is_capturing_screen=*/false, /*title=*/u"Google Meet",
|
||||||
|
/*url=*/GURL("https://meet.google.com/abc-xyz/ab-123")));
|
||||||
|
AddMediaApp(crosapi::mojom::VideoConferenceMediaAppInfo::New(
|
||||||
|
/*id=*/base::UnguessableToken::Create(),
|
||||||
|
/*last_activity_time=*/base::Time::Now(),
|
||||||
|
/*is_capturing_camera=*/false, /*is_capturing_microphone=*/true,
|
||||||
|
/*is_capturing_screen=*/true, /*title=*/u"Zoom",
|
||||||
|
/*url=*/absl::nullopt));
|
||||||
|
}
|
||||||
|
|
||||||
FakeVideoConferenceTrayController::~FakeVideoConferenceTrayController() {
|
FakeVideoConferenceTrayController::~FakeVideoConferenceTrayController() {
|
||||||
effect_repository_.reset();
|
effect_repository_.reset();
|
||||||
@ -32,4 +49,22 @@ void FakeVideoConferenceTrayController::SetMicrophoneMuted(bool muted) {
|
|||||||
CrasAudioHandler::InputMuteChangeMethod::kKeyboardButton);
|
CrasAudioHandler::InputMuteChangeMethod::kKeyboardButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FakeVideoConferenceTrayController::GetMediaApps(
|
||||||
|
base::OnceCallback<void(MediaApps)> ui_callback) {
|
||||||
|
MediaApps apps;
|
||||||
|
for (auto& app : media_apps_) {
|
||||||
|
apps.push_back(app->Clone());
|
||||||
|
}
|
||||||
|
std::move(ui_callback).Run(std::move(apps));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FakeVideoConferenceTrayController::AddMediaApp(
|
||||||
|
crosapi::mojom::VideoConferenceMediaAppInfoPtr media_app) {
|
||||||
|
media_apps_.push_back(std::move(media_app));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FakeVideoConferenceTrayController::ClearMediaApps() {
|
||||||
|
media_apps_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ash
|
} // namespace ash
|
||||||
|
@ -31,11 +31,19 @@ class ASH_EXPORT FakeVideoConferenceTrayController
|
|||||||
// VideoConferenceTrayController:
|
// VideoConferenceTrayController:
|
||||||
void SetCameraMuted(bool muted) override;
|
void SetCameraMuted(bool muted) override;
|
||||||
void SetMicrophoneMuted(bool muted) override;
|
void SetMicrophoneMuted(bool muted) override;
|
||||||
|
void GetMediaApps(base::OnceCallback<void(MediaApps)> ui_callback) override;
|
||||||
|
|
||||||
|
// Adds or clears media app(s) in `media_apps_`.
|
||||||
|
void AddMediaApp(crosapi::mojom::VideoConferenceMediaAppInfoPtr media_app);
|
||||||
|
void ClearMediaApps();
|
||||||
|
|
||||||
bool camera_muted() { return camera_muted_; }
|
bool camera_muted() { return camera_muted_; }
|
||||||
bool microphone_muted() { return microphone_muted_; }
|
bool microphone_muted() { return microphone_muted_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// A vector containing all currently running media apps. Used for testing.
|
||||||
|
MediaApps media_apps_;
|
||||||
|
|
||||||
// Indicates whether camera/microphone is muted.
|
// Indicates whether camera/microphone is muted.
|
||||||
bool camera_muted_ = false;
|
bool camera_muted_ = false;
|
||||||
bool microphone_muted_ = false;
|
bool microphone_muted_ = false;
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
namespace ash {
|
namespace ash {
|
||||||
|
|
||||||
|
using MediaApps = std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr>;
|
||||||
|
|
||||||
// Controller that will act as a "bridge" between VC apps management and the VC
|
// Controller that will act as a "bridge" between VC apps management and the VC
|
||||||
// UI layers. The singleton instance is constructed immediately before and
|
// UI layers. The singleton instance is constructed immediately before and
|
||||||
// destructed immediately after the UI, so any code that keeps a reference to
|
// destructed immediately after the UI, so any code that keeps a reference to
|
||||||
@ -69,6 +71,11 @@ class ASH_EXPORT VideoConferenceTrayController
|
|||||||
// Sets the state for microphone mute. Virtual for testing/mocking.
|
// Sets the state for microphone mute. Virtual for testing/mocking.
|
||||||
virtual void SetMicrophoneMuted(bool muted) = 0;
|
virtual void SetMicrophoneMuted(bool muted) = 0;
|
||||||
|
|
||||||
|
// Returns asynchronously a vector of media apps that will be displayed in the
|
||||||
|
// "Return to app" panel of the bubble. Virtual for testing/mocking.
|
||||||
|
virtual void GetMediaApps(
|
||||||
|
base::OnceCallback<void(MediaApps)> ui_callback) = 0;
|
||||||
|
|
||||||
// Updates the tray UI with the given `VideoConferenceMediaState`.
|
// Updates the tray UI with the given `VideoConferenceMediaState`.
|
||||||
void UpdateWithMediaState(VideoConferenceMediaState state);
|
void UpdateWithMediaState(VideoConferenceMediaState state);
|
||||||
|
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
#include "ash/constants/ash_pref_names.h"
|
#include "ash/constants/ash_pref_names.h"
|
||||||
#include "ash/session/session_controller_impl.h"
|
#include "ash/session/session_controller_impl.h"
|
||||||
#include "ash/shell.h"
|
#include "ash/shell.h"
|
||||||
|
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
|
||||||
|
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
|
||||||
|
#include "chrome/browser/ash/video_conference/video_conference_manager_ash.h"
|
||||||
#include "components/prefs/pref_service.h"
|
#include "components/prefs/pref_service.h"
|
||||||
|
|
||||||
namespace ash {
|
namespace ash {
|
||||||
@ -35,4 +38,12 @@ void VideoConferenceTrayControllerImpl::SetMicrophoneMuted(bool muted) {
|
|||||||
pref_service->SetBoolean(prefs::kUserMicrophoneAllowed, !muted);
|
pref_service->SetBoolean(prefs::kUserMicrophoneAllowed, !muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VideoConferenceTrayControllerImpl::GetMediaApps(
|
||||||
|
base::OnceCallback<void(MediaApps)> ui_callback) {
|
||||||
|
crosapi::CrosapiManager::Get()
|
||||||
|
->crosapi_ash()
|
||||||
|
->video_conference_manager_ash()
|
||||||
|
->GetMediaApps(std::move(ui_callback));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ash
|
} // namespace ash
|
||||||
|
@ -24,6 +24,7 @@ class VideoConferenceTrayControllerImpl : public VideoConferenceTrayController {
|
|||||||
// VideoConferenceTrayController:
|
// VideoConferenceTrayController:
|
||||||
void SetCameraMuted(bool muted) override;
|
void SetCameraMuted(bool muted) override;
|
||||||
void SetMicrophoneMuted(bool muted) override;
|
void SetMicrophoneMuted(bool muted) override;
|
||||||
|
void GetMediaApps(base::OnceCallback<void(MediaApps)> ui_callback) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ash
|
} // namespace ash
|
||||||
|
Reference in New Issue
Block a user