blob: 9582e22dcc12fb4c26ab1fa0a5e78a66d4e1f377 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/magic_boost/magic_boost_state_ash.h"
#include <cstdint>
#include <memory>
#include <optional>
#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/mahi/mahi_utils.h"
#include "base/check_is_test.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/scoped_observation.h"
#include "base/types/cxx23_to_underlying.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "chrome/browser/ash/input_method/editor_mediator_factory.h"
#include "chrome/browser/ash/input_method/editor_panel_manager.h"
#include "chrome/browser/ash/mahi/mahi_availability.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/components/editor_menu/public/cpp/editor_context.h"
#include "chromeos/ash/components/editor_menu/public/cpp/editor_mode.h"
#include "chromeos/components/magic_boost/public/cpp/magic_boost_state.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/user_manager/user_manager.h"
namespace ash {
namespace {
// Wait for refresh tokens load and run the provided callback. This object
// immediately runs the callback if refresh tokens are already loaded.
class RefreshTokensLoadedBarrier : public signin::IdentityManager::Observer {
public:
RefreshTokensLoadedBarrier(Profile* profile,
signin::IdentityManager* identity_manager,
base::OnceCallback<void()> callback)
: callback_(std::move(callback)) {
CHECK(profile);
CHECK(identity_manager);
if (identity_manager->AreRefreshTokensLoaded()) {
std::move(callback_).Run();
return;
}
identity_manager_observation_.Observe(identity_manager);
}
void OnRefreshTokensLoaded() override {
std::move(callback_).Run();
identity_manager_observation_.Reset();
}
private:
base::OnceCallback<void()> callback_;
base::ScopedObservation<signin::IdentityManager, RefreshTokensLoadedBarrier>
identity_manager_observation_{this};
};
} // namespace
MagicBoostStateAsh::MagicBoostStateAsh()
: MagicBoostStateAsh(
MagicBoostStateAsh::InjectActiveProfileForTestingCallback()) {}
MagicBoostStateAsh::MagicBoostStateAsh(
MagicBoostStateAsh::InjectActiveProfileForTestingCallback
inject_active_profile_for_testing_callback)
: inject_active_profile_for_testing_callback_(
inject_active_profile_for_testing_callback) {
if (!inject_active_profile_for_testing_callback_.is_null()) {
CHECK_IS_TEST();
}
shell_observation_.Observe(ash::Shell::Get());
auto* session_controller = ash::Shell::Get()->session_controller();
CHECK(session_controller);
session_observation_.Observe(session_controller);
// Register pref changes if use session already started.
if (session_controller->IsActiveUserSessionStarted()) {
PrefService* prefs = session_controller->GetPrimaryUserPrefService();
CHECK(prefs);
RegisterPrefChanges(prefs);
}
}
MagicBoostStateAsh::~MagicBoostStateAsh() {
editor_manager_for_test_ = nullptr;
}
Profile* MagicBoostStateAsh::GetActiveUserProfile() {
if (!inject_active_profile_for_testing_callback_.is_null()) {
CHECK_IS_TEST();
return inject_active_profile_for_testing_callback_.Run();
}
return Profile::FromBrowserContext(
ash::BrowserContextHelper::Get()->GetBrowserContextByUser(
user_manager::UserManager::Get()->GetActiveUser()));
}
void MagicBoostStateAsh::OnActiveUserPrefServiceChanged(
PrefService* pref_service) {
RegisterPrefChanges(pref_service);
}
bool MagicBoostStateAsh::CanShowNoticeBannerForHMR() {
PrefService* pref = pref_change_registrar_->prefs();
// TODO(b:397521071): now the kHmrEnabled is not managed, this logic needs to
// be revisited.
// Only show the notice when:
// 1. HMR is forced ON by the admin, and
// 2. The consent status is currently disabled.
return pref->IsManagedPreference(ash::prefs::kHmrEnabled) &&
pref->GetBoolean(ash::prefs::kHmrEnabled) &&
hmr_consent_status().has_value() &&
hmr_consent_status().value() == chromeos::HMRConsentStatus::kDeclined;
}
int32_t MagicBoostStateAsh::AsyncIncrementHMRConsentWindowDismissCount() {
int32_t incremented_count = hmr_consent_window_dismiss_count() + 1;
pref_change_registrar_->prefs()->SetInteger(
ash::prefs::kHMRConsentWindowDismissCount, incremented_count);
return incremented_count;
}
void MagicBoostStateAsh::AsyncWriteConsentStatus(
chromeos::HMRConsentStatus consent_status) {
pref_change_registrar_->prefs()->SetInteger(
ash::prefs::kHMRConsentStatus, base::to_underlying(consent_status));
}
void MagicBoostStateAsh::AsyncWriteHMREnabled(bool enabled) {
pref_change_registrar_->prefs()->SetBoolean(ash::prefs::kHmrEnabled, enabled);
}
void MagicBoostStateAsh::ShouldIncludeOrcaInOptIn(
base::OnceCallback<void(bool)> callback) {
GetEditorPanelManager()->GetEditorPanelContext(base::BindOnce(
[](base::OnceCallback<void(bool)> callback,
const chromeos::editor_menu::EditorContext& editor_context) {
// If the mode is not `kHardBlocked` and consent status is not set, it
// means that we should include Orca in this opt-in flow.
bool should_include_orca =
editor_context.mode !=
chromeos::editor_menu::EditorMode::kHardBlocked &&
!editor_context.consent_status_settled;
std::move(callback).Run(should_include_orca);
},
std::move(callback)));
}
bool MagicBoostStateAsh::ShouldIncludeOrcaInOptInSync() {
return GetEditorPanelManager()->ShouldOptInEditor();
}
void MagicBoostStateAsh::DisableOrcaFeature() {
GetEditorPanelManager()->OnMagicBoostPromoCardDeclined();
}
void MagicBoostStateAsh::DisableLobsterSettings() {
pref_change_registrar_->prefs()->SetBoolean(ash::prefs::kLobsterEnabled,
false);
}
void MagicBoostStateAsh::EnableOrcaFeature() {
// Note that we just need to change consent status to enable the Orca feature,
// since when Orca consent status is unset, `kOrcaEnabled` should be enabled
// by default.
GetEditorPanelManager()->OnConsentApproved();
}
input_method::EditorPanelManager* MagicBoostStateAsh::GetEditorPanelManager() {
if (editor_manager_for_test_) {
return editor_manager_for_test_;
}
return input_method::EditorMediatorFactory::GetInstance()
->GetForProfile(GetActiveUserProfile())
->panel_manager();
}
void MagicBoostStateAsh::OnShellDestroying() {
session_observation_.Reset();
shell_observation_.Reset();
}
void MagicBoostStateAsh::RegisterPrefChanges(PrefService* pref_service) {
pref_change_registrar_.reset();
if (!pref_service) {
return;
}
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(pref_service);
pref_change_registrar_->Add(
ash::prefs::kMagicBoostEnabled,
base::BindRepeating(&MagicBoostStateAsh::OnMagicBoostEnabledUpdated,
base::Unretained(this)));
pref_change_registrar_->Add(
ash::prefs::kHmrEnabled,
base::BindRepeating(&MagicBoostStateAsh::OnHMREnabledUpdated,
base::Unretained(this)));
pref_change_registrar_->Add(
ash::prefs::kHmrManagedSettings,
base::BindRepeating(&MagicBoostStateAsh::OnHMREnabledUpdated,
base::Unretained(this)));
pref_change_registrar_->Add(
ash::prefs::kHMRConsentStatus,
base::BindRepeating(&MagicBoostStateAsh::OnHMRConsentStatusUpdated,
base::Unretained(this)));
pref_change_registrar_->Add(
ash::prefs::kHMRConsentWindowDismissCount,
base::BindRepeating(
&MagicBoostStateAsh::OnHMRConsentWindowDismissCountUpdated,
base::Unretained(this)));
// Initializes the `magic_boost_enabled_` based on the current prefs settings.
UpdateMagicBoostEnabled(pref_change_registrar_->prefs()->GetBoolean(
ash::prefs::kMagicBoostEnabled));
OnHMREnabledUpdated();
OnHMRConsentStatusUpdated();
OnHMRConsentWindowDismissCountUpdated();
Profile* profile = GetActiveUserProfile();
if (!profile) {
CHECK_IS_TEST();
// Test code can bypass availability check by a flag. Run check immediately
// for that case if `profile` is nullptr.
OnRefreshTokensReady();
return;
}
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
if (!identity_manager) {
// `identity_manager` is not available under a certain condition, e.g.,
// guest session, test code. Run check immediately for those cases.
OnRefreshTokensReady();
return;
}
// Availability check contains an async operation where value is unavailable
// until refresh tokens are loaded. Run availability check after refresh token
// is loaded.
refresh_tokens_loaded_barrier_.reset(new RefreshTokensLoadedBarrier(
profile, identity_manager,
base::BindOnce(&MagicBoostStateAsh::OnRefreshTokensReady,
base::Unretained(this))));
}
base::expected<bool, chromeos::MagicBoostState::Error>
MagicBoostStateAsh::IsUserEligibleForGenAIFeaturesExpected() const {
return mahi_availability::IsMahiAvailable().transform_error(
[](mahi_availability::Error error) {
// Use switch statement to get a compile error if new error types are
// added to mahi_availability::Error.
switch (error) {
case mahi_availability::Error::kMantaFeatureBitNotReady:
return chromeos::MagicBoostState::Error::kUninitialized;
}
CHECK(false) << "Unknown mahi_availability Error enum is passed";
});
}
void MagicBoostStateAsh::OnRefreshTokensReady() {
ASSIGN_OR_RETURN(bool available, IsUserEligibleForGenAIFeaturesExpected(),
[](auto) {});
UpdateUserEligibleForGenAIFeatures(available);
}
void MagicBoostStateAsh::OnMagicBoostEnabledUpdated() {
bool enabled = pref_change_registrar_->prefs()->GetBoolean(
ash::prefs::kMagicBoostEnabled);
UpdateMagicBoostEnabled(enabled);
// Update both HMR, Orca and Lobster accordingly when `kMagicBoostEnabled` is
// changed.
AsyncWriteHMREnabled(enabled);
pref_change_registrar_->prefs()->SetBoolean(ash::prefs::kOrcaEnabled,
enabled);
pref_change_registrar_->prefs()->SetBoolean(ash::prefs::kLobsterEnabled,
enabled);
}
void MagicBoostStateAsh::OnHMREnabledUpdated() {
// Looks up both the enterprise policy controlled pref and the user controlled
// pref.
PrefService* prefs = pref_change_registrar_->prefs();
bool enabled =
prefs->GetInteger(ash::prefs::kHmrManagedSettings) !=
static_cast<int>(mahi_utils::HmrEnterprisePolicy::kDisallowed) &&
prefs->GetBoolean(ash::prefs::kHmrEnabled);
UpdateHMREnabled(enabled);
auto consent_status =
hmr_consent_status().value_or(chromeos::HMRConsentStatus::kApproved);
// The feature can be enabled through the Settings page. In that case,
// `consent_status` can be unset or declined, and we need to flip it to
// `kPending` so that when users try to access the feature, we would show the
// disclaimer UI.
if (enabled && (consent_status == chromeos::HMRConsentStatus::kUnset ||
consent_status == chromeos::HMRConsentStatus::kDeclined)) {
AsyncWriteConsentStatus(chromeos::HMRConsentStatus::kPendingDisclaimer);
}
}
void MagicBoostStateAsh::OnHMRConsentStatusUpdated() {
auto consent_status = static_cast<chromeos::HMRConsentStatus>(
pref_change_registrar_->prefs()->GetInteger(
ash::prefs::kHMRConsentStatus));
UpdateHMRConsentStatus(consent_status);
}
void MagicBoostStateAsh::OnHMRConsentWindowDismissCountUpdated() {
UpdateHMRConsentWindowDismissCount(
pref_change_registrar_->prefs()->GetInteger(
ash::prefs::kHMRConsentWindowDismissCount));
}
} // namespace ash