#include "ash/system/video_conference/video_conference_tray_controller.h"
#include <array>
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/system/anchored_nudge_data.h"
#include "ash/public/cpp/system/anchored_nudge_manager.h"
#include "ash/public/cpp/system/toast_data.h"
#include "ash/public/cpp/system/toast_manager.h"
#include "ash/public/cpp/system_tray_client.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/model/system_tray_model.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "ash/system/video_conference/video_conference_common.h"
#include "ash/system/video_conference/video_conference_tray.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/session_manager_types.h"
#include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
#include "media/capture/video/chromeos/mojom/cros_camera_service.mojom-shared.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animator.h"
namespace ash {
namespace {
constexpr char kVideoConferenceTraySpeakOnMuteOptInNudgeId[] =
"video_conference_tray_nudge_ids.speak_on_mute_opt_in";
constexpr char kVideoConferenceTraySpeakOnMuteOptInConfirmationToastId[] =
"video_conference_tray_toast_ids.speak_on_mute_opt_in_confirmation";
constexpr char kVideoConferenceTraySpeakOnMuteDetectedNudgeId[] =
"video_conference_tray_nudge_ids.speak_on_mute_detected";
constexpr char kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.microphone_use_while_hw_disabled";
constexpr char kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.microphone_use_while_sw_disabled";
constexpr char kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_use_while_hw_disabled";
constexpr char kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_use_while_sw_disabled";
constexpr char kVideoConferenceTrayBothUseWhileDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_microphone_use_while_disabled";
constexpr char kShowImageButtonAnimation[] =
"ash.vc.show_inmage_button_animation";
constexpr char kShowCreateWithAiButtonAnimation[] =
"ash.vc.show_create_with_ai_button_animation";
constexpr std::array<const char*, 6> kNudgeIds = {
kVideoConferenceTraySpeakOnMuteOptInNudgeId,
kVideoConferenceTraySpeakOnMuteDetectedNudgeId,
kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId,
kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId,
kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId,
kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId};
constexpr auto kRepeatedShowTimerInterval = base::Milliseconds(100);
constexpr auto kHandleDeviceUsedWhileDisabledWaitTime = base::Milliseconds(200);
constexpr int kSpeakOnMuteOptInNudgeMaxShownCount = 3;
constexpr int kSpeakOnMuteDetectedNudgeMaxShownCount = 4;
VideoConferenceTrayController* g_controller_instance = nullptr;
bool IsAnyShelfAutoHidden() {
for (auto* root_window_controller :
Shell::Get()->GetAllRootWindowControllers()) {
CHECK(root_window_controller);
if (root_window_controller->shelf()->auto_hide_behavior() ==
ShelfAutoHideBehavior::kAlways) {
return true;
}
}
return false;
}
VideoConferenceTray* GetVcTrayInActiveWindow() {
auto* window = Shell::Get()->GetRootWindowForNewWindows();
if (!window) {
return nullptr;
}
auto* root_window_controller = RootWindowController::ForWindow(window);
if (!root_window_controller) {
return nullptr;
}
auto* status_area_widget = root_window_controller->GetStatusAreaWidget();
if (!status_area_widget) {
return nullptr;
}
return status_area_widget->video_conference_tray();
}
PrefService* GetActiveUserPrefService() {
DCHECK(Shell::Get()->session_controller()->IsActiveUserSessionStarted());
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
DCHECK(pref_service);
return pref_service;
}
}
VideoConferenceTrayController::VideoConferenceTrayController()
: repeated_shows_timer_(
FROM_HERE,
kRepeatedShowTimerInterval,
this,
&VideoConferenceTrayController::RecordRepeatedShows) {
DCHECK(!g_controller_instance);
g_controller_instance = this;
}
VideoConferenceTrayController::~VideoConferenceTrayController() {
DCHECK_EQ(this, g_controller_instance);
g_controller_instance = nullptr;
if (initialized_) {
media::CameraHalDispatcherImpl::GetInstance()
->RemoveCameraPrivacySwitchObserver(this);
CrasAudioHandler::Get()->RemoveAudioObserver(this);
}
}
void VideoConferenceTrayController::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(kShowImageButtonAnimation, true);
registry->RegisterBooleanPref(kShowCreateWithAiButtonAnimation, true);
}
VideoConferenceTrayController* VideoConferenceTrayController::Get() {
return g_controller_instance;
}
void VideoConferenceTrayController::Initialize(
VideoConferenceManagerBase* video_conference_manager) {
DCHECK(!video_conference_manager_)
<< "VideoConferenceTrayController should not be Initialized twice.";
video_conference_manager_ = video_conference_manager;
media::CameraHalDispatcherImpl::GetInstance()->AddCameraPrivacySwitchObserver(
this);
CrasAudioHandler::Get()->AddAudioObserver(this);
Shell::Get()->AddShellObserver(this);
Shell::Get()->session_controller()->AddObserver(this);
initialized_ = true;
}
void VideoConferenceTrayController::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void VideoConferenceTrayController::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
bool VideoConferenceTrayController::ShouldShowTray() const {
DCHECK(Shell::Get());
return Shell::Get()->session_controller()->GetSessionState() ==
session_manager::SessionState::ACTIVE &&
state_.has_media_app;
}
void VideoConferenceTrayController::CreateNudgeRequest(
std::unique_ptr<AnchoredNudgeData> nudge_data) {
if (requested_nudge_data_) {
return;
}
requested_nudge_data_ = std::move(nudge_data);
auto* active_vc_tray = GetVcTrayInActiveWindow();
if (!active_vc_tray) {
return;
}
if (!active_vc_tray->layer()->GetAnimator()->is_animating()) {
MaybeRunNudgeRequest();
}
}
void VideoConferenceTrayController::MaybeRunNudgeRequest() {
if (!requested_nudge_data_) {
return;
}
AnchoredNudgeManager::Get()->Show(*requested_nudge_data_);
requested_nudge_data_.reset();
}
void VideoConferenceTrayController::MaybeShowSpeakOnMuteOptInNudge() {
auto* active_vc_tray = GetVcTrayInActiveWindow();
if (!active_vc_tray) {
return;
}
if (!active_vc_tray->visible_preferred() || !GetMicrophoneMuted()) {
return;
}
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
if (!pref_service) {
return;
}
if (!pref_service->GetBoolean(prefs::kShouldShowSpeakOnMuteOptInNudge)) {
return;
}
CloseAllVcNudges();
views::View* anchor_view = active_vc_tray->audio_icon();
if (!anchor_view->GetVisible()) {
return;
}
AnchoredNudgeData nudge_data(
kVideoConferenceTraySpeakOnMuteOptInNudgeId,
NudgeCatalogName::kVideoConferenceTraySpeakOnMuteOptIn,
l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_NUDGE_SPEAK_ON_MUTE_OPT_IN_BODY),
anchor_view);
nudge_data.image_model =
ui::ResourceBundle::GetSharedInstance().GetThemedLottieImageNamed(
IDR_SPEAK_ON_MUTE_OPT_IN_NUDGE_IMAGE);
nudge_data.title_text = l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_NUDGE_SPEAK_ON_MUTE_OPT_IN_TITLE);
nudge_data.primary_button_text = l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_NUDGE_SPEAK_ON_MUTE_OPT_IN_PRIMARY_BUTTON);
nudge_data.primary_button_callback = base::BindRepeating(
&VideoConferenceTrayController::OnSpeakOnMuteNudgeOptInAction,
weak_ptr_factory_.GetWeakPtr(), true);
nudge_data.secondary_button_text = l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_NUDGE_SPEAK_ON_MUTE_OPT_IN_SECONDARY_BUTTON);
nudge_data.secondary_button_callback = base::BindRepeating(
&VideoConferenceTrayController::OnSpeakOnMuteNudgeOptInAction,
weak_ptr_factory_.GetWeakPtr(), false);
nudge_data.duration = NudgeDuration::kLongDuration;
nudge_data.anchored_to_shelf = true;
AnchoredNudgeManager::Get()->Show(nudge_data);
pref_service->SetInteger(
prefs::kSpeakOnMuteOptInNudgeShownCount,
pref_service->GetInteger(prefs::kSpeakOnMuteOptInNudgeShownCount) + 1);
if (pref_service->GetInteger(prefs::kSpeakOnMuteOptInNudgeShownCount) >=
kSpeakOnMuteOptInNudgeMaxShownCount) {
pref_service->SetBoolean(prefs::kShouldShowSpeakOnMuteOptInNudge, false);
}
}
void VideoConferenceTrayController::DismissImageButtonAnimationForever() {
GetActiveUserPrefService()->SetBoolean(kShowImageButtonAnimation, false);
}
void VideoConferenceTrayController::
DismissCreateWithAiButtonAnimationForever() {
GetActiveUserPrefService()->SetBoolean(kShowCreateWithAiButtonAnimation,
false);
}
bool VideoConferenceTrayController::ShouldShowImageButtonAnimation() const {
return GetActiveUserPrefService()->GetBoolean(kShowImageButtonAnimation);
}
bool VideoConferenceTrayController::ShouldShowCreateWithAiButtonAnimation()
const {
return GetActiveUserPrefService()->GetBoolean(
kShowCreateWithAiButtonAnimation);
}
void VideoConferenceTrayController::OnSpeakOnMuteNudgeOptInAction(bool opt_in) {
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
if (!pref_service) {
return;
}
pref_service->SetBoolean(prefs::kShouldShowSpeakOnMuteOptInNudge, false);
pref_service->SetBoolean(prefs::kUserSpeakOnMuteDetectionEnabled, opt_in);
AnchoredNudgeManager::Get()->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTraySpeakOnMuteOptIn);
ToastData toast_data(
kVideoConferenceTraySpeakOnMuteOptInConfirmationToastId,
ToastCatalogName::kVideoConferenceTraySpeakOnMuteOptInConfirmation,
l10n_util::GetStringUTF16(
opt_in
? IDS_ASH_VIDEO_CONFERENCE_NUDGE_SPEAK_ON_MUTE_OPT_IN_CONFIRMATION_BODY
: IDS_ASH_VIDEO_CONFERENCE_NUDGE_SPEAK_ON_MUTE_OPT_OUT_CONFIRMATION_BODY));
toast_data.persist_on_hover = true;
toast_data.show_on_all_root_windows = true;
toast_data.button_type = ToastData::ButtonType::kTextButton;
toast_data.button_text = l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_NUDGE_SPEAK_ON_MUTE_OPT_IN_CONFIRMATION_BUTTON);
toast_data.button_callback = base::BindRepeating([]() {
Shell::Get()
->system_tray_model()
->client()
->ShowSpeakOnMuteDetectionSettings();
});
ToastManager::Get()->Show(std::move(toast_data));
}
void VideoConferenceTrayController::OnDlcDownloadStateFetched(
bool add_warning,
const std::u16string& feature_tile_title) {
for (auto& observer : observer_list_) {
observer.OnDlcDownloadStateChanged(add_warning, feature_tile_title);
}
}
void VideoConferenceTrayController::CloseAllVcNudges() {
for (size_t i = 0; i < std::size(kNudgeIds); ++i) {
AnchoredNudgeManager::Get()->Cancel(kNudgeIds[i]);
}
}
bool VideoConferenceTrayController::IsAnyVcNudgeShown() {
for (size_t i = 0; i < std::size(kNudgeIds); ++i) {
if (Shell::Get()->anchored_nudge_manager()->IsNudgeShown(kNudgeIds[i])) {
return true;
}
}
return false;
}
bool VideoConferenceTrayController::GetHasCameraPermissions() const {
return state_.has_camera_permission;
}
bool VideoConferenceTrayController::GetHasMicrophonePermissions() const {
return state_.has_microphone_permission;
}
void VideoConferenceTrayController::UpdateSidetoneSupportedState() {
CrasAudioHandler::Get()->UpdateSidetoneSupportedState();
}
bool VideoConferenceTrayController::IsSidetoneSupported() const {
return CrasAudioHandler::Get()->IsSidetoneSupported();
}
bool VideoConferenceTrayController::GetSidetoneEnabled() const {
return CrasAudioHandler::Get()->GetSidetoneEnabled();
}
void VideoConferenceTrayController::SetSidetoneEnabled(bool enabled) {
CrasAudioHandler::Get()->SetSidetoneEnabled(enabled);
}
void VideoConferenceTrayController::SetEwmaPowerReportEnabled(bool enabled) {
CrasAudioHandler::Get()->SetEwmaPowerReportEnabled(enabled);
}
double VideoConferenceTrayController::GetEwmaPower() {
return CrasAudioHandler::Get()->GetEwmaPower();
}
bool VideoConferenceTrayController::IsCapturingScreen() const {
return state_.is_capturing_screen;
}
bool VideoConferenceTrayController::IsCapturingCamera() const {
return state_.is_capturing_camera;
}
bool VideoConferenceTrayController::IsCapturingMicrophone() const {
return state_.is_capturing_microphone;
}
void VideoConferenceTrayController::SetCameraMuted(bool muted) {
if (camera_muted_by_hardware_switch_) {
return;
}
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
if (!pref_service) {
return;
}
pref_service->SetBoolean(prefs::kUserCameraAllowed, !muted);
}
bool VideoConferenceTrayController::GetCameraMuted() {
if (camera_muted_by_hardware_switch_) {
return true;
}
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
return pref_service && !pref_service->GetBoolean(prefs::kUserCameraAllowed);
}
void VideoConferenceTrayController::SetMicrophoneMuted(bool muted) {
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
if (!pref_service) {
return;
}
pref_service->SetBoolean(prefs::kUserMicrophoneAllowed, !muted);
}
bool VideoConferenceTrayController::GetMicrophoneMuted() {
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
return pref_service &&
!pref_service->GetBoolean(prefs::kUserMicrophoneAllowed);
}
void VideoConferenceTrayController::StopAllScreenShare() {
CHECK(video_conference_manager_);
video_conference_manager_->StopAllScreenShare();
}
void VideoConferenceTrayController::GetMediaApps(
base::OnceCallback<void(MediaApps)> ui_callback) {
CHECK(video_conference_manager_);
video_conference_manager_->GetMediaApps(std::move(ui_callback));
}
void VideoConferenceTrayController::ReturnToApp(
const base::UnguessableToken& id) {
CHECK(video_conference_manager_);
video_conference_manager_->ReturnToApp(id);
}
void VideoConferenceTrayController::OnCameraHWPrivacySwitchStateChanged(
const std::string& device_id,
cros::mojom::CameraPrivacySwitchState state) {
camera_muted_by_hardware_switch_ =
state == cros::mojom::CameraPrivacySwitchState::ON;
UpdateCameraIcons();
if (video_conference_manager_) {
video_conference_manager_->SetSystemMediaDeviceStatus(
crosapi::mojom::VideoConferenceMediaDevice::kCamera,
!GetCameraMuted());
}
if (!camera_muted_by_hardware_switch_) {
auto* nudge_manager = AnchoredNudgeManager::Get();
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileHWDisabled);
nudge_manager->Cancel(kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId);
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraMicrophoneUseWhileDisabled);
nudge_manager->Cancel(kVideoConferenceTrayBothUseWhileDisabledNudgeId);
}
}
void VideoConferenceTrayController::OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState state) {
camera_muted_by_software_switch_ =
state == cros::mojom::CameraPrivacySwitchState::ON;
UpdateCameraIcons();
if (video_conference_manager_) {
video_conference_manager_->SetSystemMediaDeviceStatus(
crosapi::mojom::VideoConferenceMediaDevice::kCamera,
!GetCameraMuted());
}
if (!camera_muted_by_software_switch_) {
auto* nudge_manager = AnchoredNudgeManager::Get();
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileSWDisabled);
nudge_manager->Cancel(kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId);
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraMicrophoneUseWhileDisabled);
nudge_manager->Cancel(kVideoConferenceTrayBothUseWhileDisabledNudgeId);
}
}
void VideoConferenceTrayController::OnInputMuteChanged(
bool mute_on,
CrasAudioHandler::InputMuteChangeMethod method) {
for (auto* root_window_controller :
Shell::Get()->GetAllRootWindowControllers()) {
DCHECK(root_window_controller);
DCHECK(root_window_controller->GetStatusAreaWidget());
auto* audio_icon = root_window_controller->GetStatusAreaWidget()
->video_conference_tray()
->audio_icon();
audio_icon->SetToggled(mute_on);
audio_icon->UpdateCapturingState();
}
if (video_conference_manager_) {
video_conference_manager_->SetSystemMediaDeviceStatus(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone,
!mute_on);
}
microphone_muted_by_hardware_switch_ =
method == CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter;
if (mute_on) {
last_speak_on_mute_nudge_shown_time_ = base::TimeTicks();
speak_on_mute_nudge_shown_count_ = 0;
MaybeShowSpeakOnMuteOptInNudge();
} else {
auto* nudge_manager = AnchoredNudgeManager::Get();
nudge_manager->Cancel(kVideoConferenceTraySpeakOnMuteOptInNudgeId);
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTraySpeakOnMuteDetected);
nudge_manager->Cancel(kVideoConferenceTraySpeakOnMuteDetectedNudgeId);
nudge_manager->MaybeRecordNudgeAction(
microphone_muted_by_hardware_switch_
? NudgeCatalogName::kVideoConferenceTrayMicrophoneUseWhileHWDisabled
: NudgeCatalogName::
kVideoConferenceTrayMicrophoneUseWhileSWDisabled);
nudge_manager->Cancel(
microphone_muted_by_hardware_switch_
? kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId
: kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId);
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraMicrophoneUseWhileDisabled);
nudge_manager->Cancel(kVideoConferenceTrayBothUseWhileDisabledNudgeId);
}
}
void VideoConferenceTrayController::OnSpeakOnMuteDetected() {
if (IsAnyVcNudgeShown()) {
return;
}
auto* active_vc_tray = GetVcTrayInActiveWindow();
if (!active_vc_tray) {
return;
}
const base::TimeTicks current_time = base::TimeTicks::Now();
if (speak_on_mute_nudge_shown_count_ == 0 ||
(speak_on_mute_nudge_shown_count_ <
kSpeakOnMuteDetectedNudgeMaxShownCount &&
(current_time - last_speak_on_mute_nudge_shown_time_).InSeconds() >=
60 * std::pow(2, speak_on_mute_nudge_shown_count_))) {
AnchoredNudgeData nudge_data(
kVideoConferenceTraySpeakOnMuteDetectedNudgeId,
NudgeCatalogName::kVideoConferenceTraySpeakOnMuteDetected,
l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_SPEAK_ON_MUTE_DETECTED),
active_vc_tray->audio_icon());
nudge_data.click_callback = base::BindRepeating([]() -> void {
Shell::Get()
->system_tray_model()
->client()
->ShowSpeakOnMuteDetectionSettings();
});
nudge_data.anchored_to_shelf = true;
AnchoredNudgeManager::Get()->Show(nudge_data);
last_speak_on_mute_nudge_shown_time_ = current_time;
++speak_on_mute_nudge_shown_count_;
}
}
void VideoConferenceTrayController::OnUserSessionAdded(
const AccountId& account_id) {
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
if (!pref_service) {
return;
}
}
void VideoConferenceTrayController::OnShellDestroying() {
Shell::Get()->session_controller()->RemoveObserver(this);
Shell::Get()->RemoveShellObserver(this);
}
void VideoConferenceTrayController::HandleClientUpdate(
crosapi::mojom::VideoConferenceClientUpdatePtr update) {
if (update->added_or_removed_app ==
crosapi::mojom::VideoConferenceAppUpdate::kAppAdded) {
OnAppAdded();
}
}
void VideoConferenceTrayController::OnAppAdded() {
if (!IsAnyShelfAutoHidden()) {
return;
}
if (disable_shelf_autohide_locks_.empty()) {
for (auto* root_window_controller :
Shell::Get()->GetAllRootWindowControllers()) {
CHECK(root_window_controller);
CHECK(root_window_controller->shelf());
disable_shelf_autohide_locks_.emplace_back(
root_window_controller->shelf());
}
}
disable_shelf_autohide_timer_.Start(
FROM_HERE, base::Seconds(6),
base::BindOnce(
[](std::list<Shelf::ScopedDisableAutoHide>&
disable_shelf_autohide_locks) {
disable_shelf_autohide_locks.clear();
},
std::ref(disable_shelf_autohide_locks_)));
}
base::OneShotTimer&
VideoConferenceTrayController::GetShelfAutoHideTimerForTest() {
return disable_shelf_autohide_timer_;
}
VideoConferenceTrayEffectsManager&
VideoConferenceTrayController::GetEffectsManager() {
return effects_manager_;
}
void VideoConferenceTrayController::CreateBackgroundImage() {
CHECK(video_conference_manager_);
video_conference_manager_->CreateBackgroundImage();
}
void VideoConferenceTrayController::UpdateWithMediaState(
VideoConferenceMediaState state) {
auto old_state = state_;
const bool old_tray_target_visibility = ShouldShowTray();
state_ = state;
const bool new_tray_target_visibility = ShouldShowTray();
if (new_tray_target_visibility && !old_tray_target_visibility) {
GetEffectsManager().RecordInitialStates();
++count_repeated_shows_;
repeated_shows_timer_.Reset();
last_speak_on_mute_nudge_shown_time_ = base::TimeTicks();
speak_on_mute_nudge_shown_count_ = 0;
}
if (state_.has_media_app != old_state.has_media_app) {
for (auto& observer : observer_list_) {
observer.OnHasMediaAppStateChange();
}
}
if (state_.has_camera_permission != old_state.has_camera_permission) {
for (auto& observer : observer_list_) {
observer.OnCameraPermissionStateChange();
}
}
if (state_.has_microphone_permission != old_state.has_microphone_permission) {
for (auto& observer : observer_list_) {
observer.OnMicrophonePermissionStateChange();
}
}
if (state_.is_capturing_camera != old_state.is_capturing_camera) {
for (auto& observer : observer_list_) {
observer.OnCameraCapturingStateChange(state_.is_capturing_camera);
}
}
if (state_.is_capturing_microphone != old_state.is_capturing_microphone) {
for (auto& observer : observer_list_) {
observer.OnMicrophoneCapturingStateChange(state_.is_capturing_microphone);
}
}
if (state_.is_capturing_screen != old_state.is_capturing_screen) {
for (auto& observer : observer_list_) {
observer.OnScreenSharingStateChange(state_.is_capturing_screen);
}
}
if (state_.has_media_app) {
return;
}
if (disable_shelf_autohide_timer_.IsRunning()) {
disable_shelf_autohide_timer_.Stop();
}
disable_shelf_autohide_locks_.clear();
}
bool VideoConferenceTrayController::HasCameraPermission() const {
return state_.has_camera_permission;
}
bool VideoConferenceTrayController::HasMicrophonePermission() const {
return state_.has_microphone_permission;
}
void VideoConferenceTrayController::HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice device,
const std::u16string& app_name) {
if (device == crosapi::mojom::VideoConferenceMediaDevice::kUnusedDefault) {
return;
}
UsedWhileDisabledNudgeType type = GetUsedWhileDisabledNudgeType(device);
if (!use_while_disabled_signal_waiter_.IsRunning()) {
use_while_disabled_nudge_on_wait_ = type;
use_while_disabled_signal_waiter_.Start(
FROM_HERE, kHandleDeviceUsedWhileDisabledWaitTime,
base::BindOnce(
&VideoConferenceTrayController::DisplayUsedWhileDisabledNudge,
weak_ptr_factory_.GetWeakPtr(), type, app_name));
return;
}
if (type == use_while_disabled_nudge_on_wait_) {
return;
}
use_while_disabled_signal_waiter_.Stop();
DisplayUsedWhileDisabledNudge(UsedWhileDisabledNudgeType::kBoth, app_name);
}
void VideoConferenceTrayController::UpdateCameraIcons() {
for (auto* root_window_controller :
Shell::Get()->GetAllRootWindowControllers()) {
DCHECK(root_window_controller);
DCHECK(root_window_controller->GetStatusAreaWidget());
auto* camera_icon = root_window_controller->GetStatusAreaWidget()
->video_conference_tray()
->camera_icon();
camera_icon->SetToggled(camera_muted_by_hardware_switch_ ||
camera_muted_by_software_switch_);
camera_icon->UpdateCapturingState();
}
}
void VideoConferenceTrayController::RecordRepeatedShows() {
if (count_repeated_shows_ == 0) {
return;
}
base::UmaHistogramCounts100("Ash.VideoConference.NumberOfRepeatedShows",
count_repeated_shows_);
count_repeated_shows_ = 0;
}
void VideoConferenceTrayController::DisplayUsedWhileDisabledNudge(
VideoConferenceTrayController::UsedWhileDisabledNudgeType type,
const std::u16string& app_name) {
if (IsAnyVcNudgeShown()) {
return;
}
auto* active_vc_tray = GetVcTrayInActiveWindow();
if (!active_vc_tray) {
return;
}
std::u16string device_name;
int text_id;
NudgeCatalogName catalog_name;
std::string nudge_id;
views::View* anchor_view = nullptr;
switch (type) {
case VideoConferenceTrayController::UsedWhileDisabledNudgeType::kMicrophone:
device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME);
if (microphone_muted_by_hardware_switch_) {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED;
nudge_id = kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayMicrophoneUseWhileHWDisabled;
} else {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED;
nudge_id = kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayMicrophoneUseWhileSWDisabled;
}
anchor_view = active_vc_tray->audio_icon();
break;
case VideoConferenceTrayController::UsedWhileDisabledNudgeType::kCamera:
device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME);
if (camera_muted_by_hardware_switch_) {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED;
nudge_id = kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileHWDisabled;
} else {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED;
nudge_id = kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileSWDisabled;
}
anchor_view = active_vc_tray->camera_icon();
break;
case VideoConferenceTrayController::UsedWhileDisabledNudgeType::kBoth:
device_name = l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_CAMERA_MICROPHONE_NAME);
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED;
nudge_id = kVideoConferenceTrayBothUseWhileDisabledNudgeId;
catalog_name = NudgeCatalogName::
kVideoConferenceTrayCameraMicrophoneUseWhileDisabled;
anchor_view = active_vc_tray->audio_icon();
break;
default:
NOTREACHED();
}
AnchoredNudgeData nudge_data(
nudge_id, catalog_name,
l10n_util::GetStringFUTF16(text_id, app_name, device_name), anchor_view);
nudge_data.anchored_to_shelf = true;
CreateNudgeRequest(
std::make_unique<AnchoredNudgeData>(std::move(nudge_data)));
}
VideoConferenceTrayController::UsedWhileDisabledNudgeType
VideoConferenceTrayController::GetUsedWhileDisabledNudgeType(
crosapi::mojom::VideoConferenceMediaDevice device) {
DCHECK_NE(device, crosapi::mojom::VideoConferenceMediaDevice::kUnusedDefault);
VideoConferenceTrayController::UsedWhileDisabledNudgeType type;
switch (device) {
case crosapi::mojom::VideoConferenceMediaDevice::kCamera:
type = VideoConferenceTrayController::UsedWhileDisabledNudgeType::kCamera;
break;
case crosapi::mojom::VideoConferenceMediaDevice::kMicrophone:
type = VideoConferenceTrayController::UsedWhileDisabledNudgeType::
kMicrophone;
break;
default:
NOTREACHED();
}
return type;
}
}