blob: 7af7d6e6a985b156f6f145bfb163718a9a86d1fa [file] [log] [blame]
// Copyright 2020 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/ui/quick_answers/quick_answers_ui_controller.h"
#include "base/bind.h"
#include "base/strings/escape.h"
#include "base/strings/stringprintf.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/ui/quick_answers/quick_answers_controller_impl.h"
#include "chromeos/components/quick_answers/public/cpp/quick_answers_state.h"
#include "chromeos/components/quick_answers/quick_answers_model.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/public/cpp/new_window_delegate.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_commands.h" // nogncheck
#include "chrome/browser/ui/browser_finder.h" // nogncheck
#include "chrome/browser/ui/browser_navigator.h" // nogncheck
#include "chrome/browser/ui/browser_navigator_params.h" // nogncheck
#include "chromeos/crosapi/mojom/url_handler.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
namespace {
using quick_answers::QuickAnswer;
using quick_answers::QuickAnswersExitPoint;
constexpr char kGoogleSearchUrlPrefix[] = "https://www.google.com/search?q=";
constexpr char kGoogleTranslateUrlTemplate[] =
"https://translate.google.com/?sl=auto&tl=%s&text=%s&op=translate";
constexpr char kFeedbackDescriptionTemplate[] = "#QuickAnswers\nQuery:%s\n";
constexpr char kTranslationQueryPrefix[] = "Translate:";
constexpr char kQuickAnswersSettingsUrl[] =
"chrome://os-settings/osSearch/search";
// Open the specified URL in a new tab in the primary browser.
void OpenUrl(const GURL& url) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
ash::NewWindowDelegate::GetInstance()->OpenUrl(
url, ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
ash::NewWindowDelegate::Disposition::kNewForegroundTab);
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
Profile* profile = ProfileManager::GetPrimaryUserProfile();
NavigateParams navigate_params(
profile, url,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_FROM_API));
navigate_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
navigate_params.window_action = NavigateParams::SHOW_WINDOW;
Navigate(&navigate_params);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
} // namespace
QuickAnswersUiController::QuickAnswersUiController(
QuickAnswersControllerImpl* controller)
: controller_(controller) {}
QuickAnswersUiController::~QuickAnswersUiController() = default;
void QuickAnswersUiController::CreateQuickAnswersView(const gfx::Rect& bounds,
const std::string& title,
const std::string& query,
bool is_internal) {
// Currently there are timing issues that causes the quick answers view is not
// dismissed. TODO(updowndota): Remove the special handling after the root
// cause is found.
if (IsShowingQuickAnswersView()) {
LOG(ERROR) << "Quick answers view not dismissed.";
CloseQuickAnswersView();
}
DCHECK(!IsShowingUserConsentView());
SetActiveQuery(query);
// Owned by view hierarchy.
auto* const quick_answers_view = new QuickAnswersView(
bounds, title, is_internal, weak_factory_.GetWeakPtr());
quick_answers_view_tracker_.SetView(quick_answers_view);
quick_answers_view->GetWidget()->ShowInactive();
}
void QuickAnswersUiController::OnQuickAnswersViewPressed() {
// Route dismissal through |controller_| for logging impressions.
controller_->DismissQuickAnswers(QuickAnswersExitPoint::kQuickAnswersClick);
// TODO(b/240619915): Refactor so that we can access the request metadata
// instead of just the query itself.
if (base::StartsWith(query_, kTranslationQueryPrefix)) {
auto query_text = base::EscapeUrlEncodedData(
query_.substr(strlen(kTranslationQueryPrefix)), /*use_plus=*/true);
auto device_language =
l10n_util::GetLanguage(QuickAnswersState::Get()->application_locale());
auto translate_url =
base::StringPrintf(kGoogleTranslateUrlTemplate, device_language.c_str(),
query_text.c_str());
OpenUrl(GURL(translate_url));
} else {
OpenUrl(GURL(kGoogleSearchUrlPrefix +
base::EscapeUrlEncodedData(query_, /*use_plus=*/true)));
}
controller_->OnQuickAnswerClick();
}
bool QuickAnswersUiController::CloseQuickAnswersView() {
if (IsShowingQuickAnswersView()) {
quick_answers_view()->GetWidget()->Close();
return true;
}
return false;
}
void QuickAnswersUiController::OnRetryLabelPressed() {
controller_->OnRetryQuickAnswersRequest();
}
void QuickAnswersUiController::RenderQuickAnswersViewWithResult(
const gfx::Rect& anchor_bounds,
const QuickAnswer& quick_answer) {
if (!IsShowingQuickAnswersView())
return;
// QuickAnswersView was initiated with a loading page and will be updated
// when quick answers result from server side is ready.
quick_answers_view()->UpdateView(anchor_bounds, quick_answer);
}
void QuickAnswersUiController::SetActiveQuery(const std::string& query) {
query_ = query;
}
void QuickAnswersUiController::ShowRetry() {
if (!IsShowingQuickAnswersView())
return;
quick_answers_view()->ShowRetryView();
}
void QuickAnswersUiController::UpdateQuickAnswersBounds(
const gfx::Rect& anchor_bounds) {
if (IsShowingQuickAnswersView())
quick_answers_view()->UpdateAnchorViewBounds(anchor_bounds);
if (IsShowingUserConsentView())
user_consent_view()->UpdateAnchorViewBounds(anchor_bounds);
}
void QuickAnswersUiController::CreateUserConsentView(
const gfx::Rect& anchor_bounds,
const std::u16string& intent_type,
const std::u16string& intent_text) {
DCHECK(!IsShowingQuickAnswersView());
DCHECK(!IsShowingUserConsentView());
// Owned by view hierarchy.
auto* const user_consent_view = new quick_answers::UserConsentView(
anchor_bounds, intent_type, intent_text, weak_factory_.GetWeakPtr());
user_consent_view_tracker_.SetView(user_consent_view);
user_consent_view->GetWidget()->ShowInactive();
}
void QuickAnswersUiController::CloseUserConsentView() {
if (IsShowingUserConsentView()) {
user_consent_view()->GetWidget()->Close();
}
}
void QuickAnswersUiController::OnSettingsButtonPressed() {
// Route dismissal through |controller_| for logging impressions.
controller_->DismissQuickAnswers(QuickAnswersExitPoint::kSettingsButtonClick);
#if BUILDFLAG(IS_CHROMEOS_ASH)
OpenUrl(GURL(kQuickAnswersSettingsUrl));
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
// OS settings app is implemented in Ash, but OpenUrl here does not qualify
// for redirection in Lacros due to security limitations. Thus we need to
// explicitly send the request to Ash to launch the OS settings app.
chromeos::LacrosService* service = chromeos::LacrosService::Get();
DCHECK(service->IsAvailable<crosapi::mojom::UrlHandler>());
service->GetRemote<crosapi::mojom::UrlHandler>()->OpenUrl(
GURL(kQuickAnswersSettingsUrl));
#endif
}
void QuickAnswersUiController::OnReportQueryButtonPressed() {
controller_->DismissQuickAnswers(
QuickAnswersExitPoint::kReportQueryButtonClick);
auto feedback_template =
base::StringPrintf(kFeedbackDescriptionTemplate, query_.c_str());
// TODO(b/229007013): Merge the logics after resolve the deps cycle with
// //c/b/ui in ash chrome build.
#if BUILDFLAG(IS_CHROMEOS_ASH)
ash::NewWindowDelegate::GetPrimary()->OpenFeedbackPage(
ash::NewWindowDelegate::FeedbackSource::kFeedbackSourceQuickAnswers,
feedback_template);
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
chrome::OpenFeedbackDialog(
chrome::FindBrowserWithActiveWindow(),
chrome::FeedbackSource::kFeedbackSourceQuickAnswers, feedback_template);
#endif
}
void QuickAnswersUiController::OnUserConsentResult(bool consented) {
DCHECK(IsShowingUserConsentView());
controller_->OnUserConsentResult(consented);
if (consented && IsShowingQuickAnswersView())
quick_answers_view()->RequestFocus();
}
bool QuickAnswersUiController::IsShowingUserConsentView() const {
return user_consent_view_tracker_.view() &&
!user_consent_view_tracker_.view()->GetWidget()->IsClosed();
}
bool QuickAnswersUiController::IsShowingQuickAnswersView() const {
return quick_answers_view_tracker_.view() &&
!quick_answers_view_tracker_.view()->GetWidget()->IsClosed();
}