blob: cb99fbcc0a7afa9bcb80ec64a77623bf3599dcd7 [file]
// 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 "ash/lobster/lobster_controller.h"
#include <memory>
#include "ash/constants/ash_features.h"
#include "ash/lobster/lobster_entry_point_enums.h"
#include "ash/lobster/lobster_metrics_recorder.h"
#include "ash/lobster/lobster_session_impl.h"
#include "ash/public/cpp/lobster/lobster_client.h"
#include "ash/public/cpp/lobster/lobster_client_factory.h"
#include "ash/public/cpp/lobster/lobster_enums.h"
#include "ash/public/cpp/lobster/lobster_metrics_state_enums.h"
#include "ash/public/cpp/lobster/lobster_system_state.h"
#include "ash/shell.h"
namespace ash {
namespace {
bool HasOpportunityToShow(
const ash::LobsterSystemState::SystemChecks& system_checks) {
return (!system_checks.Has(ash::LobsterSystemCheck::kInvalidFeatureFlags) &&
!system_checks.Has(ash::LobsterSystemCheck::kUnsupportedHardware) &&
!system_checks.Has(ash::LobsterSystemCheck::kUnsupportedInKioskMode) &&
!system_checks.Has(ash::LobsterSystemCheck::kInvalidInputField));
}
void RecordEntryPointImpressionMetric(LobsterEntryPoint entry_point) {
switch (entry_point) {
case LobsterEntryPoint::kQuickInsert:
RecordLobsterState(LobsterMetricState::kQuickInsertTriggerImpression);
return;
case LobsterEntryPoint::kRightClickMenu:
RecordLobsterState(LobsterMetricState::kRightClickTriggerImpression);
return;
}
}
void RecordEntryPointConsentRequiredMetric(LobsterEntryPoint entry_point) {
switch (entry_point) {
case LobsterEntryPoint::kQuickInsert:
RecordLobsterState(LobsterMetricState::kQuickInsertTriggerNeedsConsent);
return;
case LobsterEntryPoint::kRightClickMenu:
RecordLobsterState(LobsterMetricState::kRightClickTriggerNeedsConsent);
return;
}
}
} // namespace
LobsterController::Trigger::Trigger(
std::unique_ptr<LobsterClient> client,
LobsterEntryPoint entry_point,
LobsterMode mode,
const LobsterTextInputContext& text_input_context)
: client_(std::move(client)),
state_(State::kReady),
entry_point_(entry_point),
mode_(mode),
text_input_context_(text_input_context) {}
LobsterController::Trigger::~Trigger() = default;
void LobsterController::Trigger::Fire(std::optional<std::string> query) {
if (state_ == State::kDisabled) {
return;
}
state_ = State::kDisabled;
if (ash::Shell::Get() == nullptr) {
return;
}
LobsterController* controller = ash::Shell::Get()->lobster_controller();
if (controller == nullptr) {
return;
}
controller->StartSession(std::move(client_), std::move(query), entry_point_,
mode_, text_input_context_);
}
LobsterController::LobsterController() = default;
LobsterController::~LobsterController() = default;
void LobsterController::SetClientFactory(LobsterClientFactory* client_factory) {
client_factory_ = client_factory;
}
std::unique_ptr<LobsterController::Trigger> LobsterController::CreateTrigger(
LobsterEntryPoint entry_point,
ui::TextInputClient* text_input_client) {
if (client_factory_ == nullptr) {
return nullptr;
}
std::unique_ptr<LobsterClient> client = client_factory_->CreateClient();
if (client == nullptr) {
return nullptr;
}
// Lobster is only triggered from focused text fields. If no text input client
// is found, never create a trigger.
if (text_input_client == nullptr) {
return nullptr;
}
LobsterTextInputContext text_input_context(
/*text_input_type=*/text_input_client->GetTextInputType(),
/*caret_bounds=*/text_input_client->GetCaretBounds(),
/*support_image_insertion=*/text_input_client->CanInsertImage());
LobsterSystemState system_state = client->GetSystemState(text_input_context);
// Records the Opportunity metric. This metric is recorded in the following
// cases:
// * Lobster is shown.
// * Lobster is being blocked, but is likely to be shown for the current
// context in the future.
if (HasOpportunityToShow(system_state.failed_checks)) {
RecordLobsterState(LobsterMetricState::kShownOpportunity);
}
if (system_state.status == LobsterStatus::kBlocked) {
RecordLobsterState(LobsterMetricState::kBlocked);
for (auto failed_reason : system_state.failed_checks) {
RecordLobsterBlockedReason(failed_reason);
}
return nullptr;
}
if (system_state.status == LobsterStatus::kConsentNeeded) {
RecordEntryPointConsentRequiredMetric(entry_point);
}
// Creates the trigger point if the status is either enabled, or requires
// consent.
RecordEntryPointImpressionMetric(entry_point);
return std::make_unique<Trigger>(std::move(client), entry_point,
text_input_context.support_image_insertion
? LobsterMode::kInsert
: LobsterMode::kDownload,
std::move(text_input_context));
}
void LobsterController::LoadUIFromCachedContext() {
if (active_session_ == nullptr) {
return;
}
active_session_->LoadUIFromCachedContext();
}
void LobsterController::StartSession(
std::unique_ptr<LobsterClient> client,
std::optional<std::string> query,
LobsterEntryPoint entry_point,
LobsterMode mode,
const LobsterTextInputContext& text_input_context) {
// Before creating a new session, we need to inform the lobster client and
// lobster session to clear their pointer to the session that is about to be
// destroyed. This is to prevent them from holding a dangling pointer to the
// old session when a new session is created.
// TODO: crbug.com/371484317 - refactors the following logic to handle the
// session creation better.
client->SetActiveSession(nullptr);
LobsterClient* lobster_client_ptr = client.get();
active_session_ = std::make_unique<LobsterSessionImpl>(std::move(client),
entry_point, mode);
lobster_client_ptr->SetActiveSession(active_session_.get());
LobsterStatus lobster_status =
lobster_client_ptr->GetSystemState(text_input_context).status;
// When LobsterForceShowDisclaimer flag is enabled, we will show the Lobster
// Disclaimer screen even when Lobster consent status has been approved
// before.
if (lobster_status == LobsterStatus::kEnabled &&
ash::features::IsLobsterAlwaysShowDisclaimerForTesting()) {
lobster_status = LobsterStatus::kConsentNeeded;
}
switch (lobster_status) {
case LobsterStatus::kConsentNeeded:
active_session_->ShowDisclaimerUIAndCacheContext(
query, text_input_context.caret_bounds);
return;
case LobsterStatus::kEnabled:
active_session_->LoadUI(query, mode, text_input_context.caret_bounds);
return;
case LobsterStatus::kBlocked:
return;
}
}
} // namespace ash