// 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/effects/video_conference_tray_effects_manager.h"
#include <memory>
#include <string>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/video_conference/bubble/vc_tile_ui_controller.h"
#include "ash/system/video_conference/effects/video_conference_tray_effects_delegate.h"
#include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
#include "ash/system/video_conference/video_conference_utils.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/observer_list.h"
#include "base/strings/string_util.h"
#include "components/live_caption/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/soda/constants.h"
#include "components/soda/soda_installer.h"
namespace ash {
VideoConferenceTrayEffectsManager::VideoConferenceTrayEffectsManager() =
default;
VideoConferenceTrayEffectsManager::~VideoConferenceTrayEffectsManager() =
default;
void VideoConferenceTrayEffectsManager::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void VideoConferenceTrayEffectsManager::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void VideoConferenceTrayEffectsManager::RegisterDelegate(
VcEffectsDelegate* delegate) {
DCHECK(delegate);
DCHECK(!IsDelegateRegistered(delegate));
effect_delegates_.push_back(delegate);
// TODO(b/345831029): Test that a VcTileUiController is reset when an effect
// is removed.
if (features::IsVcDlcUiEnabled()) {
delegate->set_on_effect_will_be_removed_callback(base::BindRepeating(
&VideoConferenceTrayEffectsManager::RemoveTileControllers,
weak_factory_.GetWeakPtr()));
}
}
void VideoConferenceTrayEffectsManager::UnregisterDelegate(
VcEffectsDelegate* delegate) {
DCHECK(delegate);
size_t num_items_erased =
std::erase_if(effect_delegates_,
[delegate](VcEffectsDelegate* d) { return delegate == d; });
DCHECK_EQ(num_items_erased, 1UL);
if (features::IsVcDlcUiEnabled()) {
// Remove tile controllers for effects hosted by the delegate being
// unregistered.
RemoveTileControllers(delegate);
}
}
bool VideoConferenceTrayEffectsManager::IsDelegateRegistered(
VcEffectsDelegate* delegate) {
DCHECK(delegate);
return std::find_if(effect_delegates_.begin(), effect_delegates_.end(),
[delegate](VcEffectsDelegate* d) {
return delegate == d;
}) != effect_delegates_.end();
}
bool VideoConferenceTrayEffectsManager::HasToggleEffects() {
return GetTotalToggleEffectButtons().size() > 0;
}
VideoConferenceTrayEffectsManager::EffectDataTable
VideoConferenceTrayEffectsManager::GetToggleEffectButtonTable() {
EffectDataVector total_buttons = GetTotalToggleEffectButtons();
EffectDataVector row;
EffectDataTable buttons;
int num_buttons = total_buttons.size();
if (num_buttons == 0) {
return buttons;
}
if (num_buttons <= 3) {
// For 3 or fewer, `effects_buttons` is the entire row.
row = total_buttons;
buttons.push_back(row);
return buttons;
}
// Max of 2 per row.
for (int i = 0; i < num_buttons; ++i) {
row.push_back(total_buttons[i]);
if (i % 2 == 1 || i == num_buttons - 1) {
buttons.push_back(row);
row.clear();
}
}
return buttons;
}
bool VideoConferenceTrayEffectsManager::HasSetValueEffects() {
return GetSetValueEffects().size() > 0;
}
VideoConferenceTrayEffectsManager::EffectDataVector
VideoConferenceTrayEffectsManager::GetSetValueEffects() {
EffectDataVector effects;
for (ash::VcEffectsDelegate* delegate : effect_delegates_) {
for (auto* effect : delegate->GetEffects(VcEffectType::kSetValue)) {
effects.push_back(effect);
}
}
return effects;
}
VideoConferenceTrayEffectsManager::EffectDataVector
VideoConferenceTrayEffectsManager::GetToggleEffects() {
EffectDataVector effects;
for (ash::VcEffectsDelegate* delegate : effect_delegates_) {
for (auto* effect : delegate->GetEffects(VcEffectType::kToggle)) {
effects.push_back(effect);
}
}
return effects;
}
void VideoConferenceTrayEffectsManager::NotifyEffectSupportStateChanged(
VcEffectId effect_id,
bool is_supported) {
for (auto& observer : observers_) {
observer.OnEffectSupportStateChanged(effect_id, is_supported);
}
}
void VideoConferenceTrayEffectsManager::RecordInitialStates() {
for (ash::VcEffectsDelegate* delegate : effect_delegates_) {
delegate->RecordInitialStates();
}
}
video_conference::VcTileUiController*
VideoConferenceTrayEffectsManager::GetUiControllerForEffectId(
VcEffectId effect_id) {
CHECK(features::IsVcDlcUiEnabled());
if (!base::Contains(controller_for_effect_id_, effect_id)) {
for (auto* effect : GetToggleEffects()) {
if (effect_id == effect->id()) {
controller_for_effect_id_[effect_id] =
std::make_unique<video_conference::VcTileUiController>(effect);
break;
}
}
}
return controller_for_effect_id_[effect_id].get();
}
std::vector<std::string>
VideoConferenceTrayEffectsManager::GetDlcIdsForEffectId(VcEffectId effect_id) {
switch (effect_id) {
case VcEffectId::kLiveCaption: {
PrefService* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
std::string locale = pref_service
? prefs::GetLiveCaptionLanguageCode(pref_service)
: speech::kUsEnglishLocale;
std::string dlc_name =
speech::SodaInstaller::GetInstance()->GetLanguageDlcNameForLocale(
locale);
// Should always have a language DLC lib for a specific language.
CHECK(!dlc_name.empty());
// "Live caption" requires both a binary ("libsoda") as well as a specific
// language model (e.g. "libsoda-model-en-us") to operate.
return {"libsoda", dlc_name};
}
case VcEffectId::kBackgroundBlur:
case VcEffectId::kPortraitRelighting:
return {"ml-core-internal"};
case VcEffectId::kTestEffect:
case VcEffectId::kNoiseCancellation:
case VcEffectId::kStyleTransfer:
case VcEffectId::kCameraFraming:
case VcEffectId::kFaceRetouch:
case VcEffectId::kStudioLook:
return {};
}
}
VideoConferenceTrayEffectsManager::EffectDataVector
VideoConferenceTrayEffectsManager::GetTotalToggleEffectButtons() {
EffectDataVector effects;
for (ash::VcEffectsDelegate* delegate : effect_delegates_) {
for (auto* effect : delegate->GetEffects(VcEffectType::kToggle)) {
effects.push_back(effect);
}
}
return effects;
}
void VideoConferenceTrayEffectsManager::RemoveTileControllers(
VcEffectsDelegate* delegate) {
if (!features::IsVcDlcUiEnabled()) {
return;
}
for (auto* effect : delegate->GetEffects(VcEffectType::kToggle)) {
const VcEffectId id = effect->id();
if (base::Contains(controller_for_effect_id_, id)) {
controller_for_effect_id_.erase(id);
}
}
}
} // namespace ash