blob: 98f94bc52ea392bd8c266239cfda1ec7cb3a5202 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/assistant/proactive_suggestions_loader.h"
#include "ash/public/cpp/assistant/proactive_suggestions.h"
#include "ash/public/cpp/assistant/util/histogram_util.h"
#include "base/i18n/rtl.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/services/assistant/public/features.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/url_util.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
namespace {
constexpr net::NetworkTrafficAnnotationTag kNetworkTrafficAnnotationTag =
net::DefineNetworkTrafficAnnotation("proactive_suggestions_loader", R"(
semantics: {
sender: "Google Assistant Proactive Suggestions"
description:
"The Google Assistant requests proactive content suggestions "
"based on the currently active browser context."
trigger:
"Change of active browser context."
destination: GOOGLE_OWNED_SERVICE
}
policy: {
cookies_allowed: YES
setting:
"The Google Assistant can be enabled/disabled in Chrome Settings "
"and is subject to eligibility requirements. The user may also "
"separately opt out of sharing screen context with Assistant."
})");
// Helpers ---------------------------------------------------------------------
// Returns the url to retrieve proactive content suggestions for a given |url|.
GURL CreateProactiveSuggestionsUrl(const GURL& url) {
constexpr char kProactiveSuggestionsEndpoint[] =
"https://assistant.google.com/proactivesuggestions/embeddedview";
GURL result = GURL(kProactiveSuggestionsEndpoint);
// The proactive suggestions service needs to be told which experiments, if
// any, to trigger on the backend.
const std::string experiment_ids = chromeos::assistant::features::
GetProactiveSuggestionsServerExperimentIds();
if (!experiment_ids.empty()) {
constexpr char kExperimentIdsParamKey[] = "experimentIds";
result = net::AppendOrReplaceQueryParameter(result, kExperimentIdsParamKey,
experiment_ids);
}
// The proactive suggestions service needs to be aware of the device locale.
constexpr char kLocaleParamKey[] = "hl";
result = net::AppendOrReplaceQueryParameter(
result, kLocaleParamKey, base::i18n::GetConfiguredLocale());
// The proactive suggestions service needs to be informed of the given |url|.
constexpr char kUrlParamKey[] = "url";
return net::AppendOrReplaceQueryParameter(result, kUrlParamKey, url.spec());
}
// Parses proactive suggestions metadata from the specified |headers|.
void ParseProactiveSuggestionsMetadata(const net::HttpResponseHeaders& headers,
int* category,
std::string* description,
std::string* search_query,
bool* has_content) {
constexpr char kCategoryHeaderName[] =
"x-assistant-proactive-suggestions-page-category";
constexpr char kDescriptionHeaderName[] =
"x-assistant-proactive-suggestions-description";
constexpr char kHasContentHeaderName[] =
"x-assistant-proactive-suggestions-has-ui-content";
constexpr char kSearchQueryHeaderName[] =
"x-assistant-proactive-suggestions-search-query";
DCHECK_EQ(ash::ProactiveSuggestions::kCategoryUnknown, *category);
DCHECK(description->empty());
DCHECK(search_query->empty());
DCHECK(!(*has_content));
size_t it = 0;
std::string name;
std::string value;
while (headers.EnumerateHeaderLines(&it, &name, &value)) {
if (name == kCategoryHeaderName) {
DCHECK_EQ(ash::ProactiveSuggestions::kCategoryUnknown, *category);
if (!base::StringToInt(value, category))
NOTREACHED();
continue;
}
if (name == kDescriptionHeaderName) {
DCHECK(description->empty());
*description = value;
continue;
}
if (name == kHasContentHeaderName) {
DCHECK(!(*has_content));
*has_content = (value == "1");
continue;
}
if (name == kSearchQueryHeaderName) {
DCHECK(search_query->empty());
*search_query = value;
}
}
}
} // namespace
// ProactiveSuggestionsLoader --------------------------------------------------
ProactiveSuggestionsLoader::ProactiveSuggestionsLoader(Profile* profile,
const GURL& url)
: profile_(profile), url_(url) {}
ProactiveSuggestionsLoader::~ProactiveSuggestionsLoader() {
if (complete_callback_)
std::move(complete_callback_).Run(/*proactive_suggestions=*/nullptr);
}
void ProactiveSuggestionsLoader::Start(CompleteCallback complete_callback) {
DCHECK(!loader_);
complete_callback_ = std::move(complete_callback);
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = CreateProactiveSuggestionsUrl(url_);
loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
kNetworkTrafficAnnotationTag);
auto* url_loader_factory =
content::BrowserContext::GetDefaultStoragePartition(profile_)
->GetURLLoaderFactoryForBrowserProcess()
.get();
auto simple_url_loader_complete_callback =
base::BindOnce(&ProactiveSuggestionsLoader::OnSimpleURLLoaderComplete,
base::Unretained(this));
loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory, std::move(simple_url_loader_complete_callback));
}
void ProactiveSuggestionsLoader::OnSimpleURLLoaderComplete(
std::unique_ptr<std::string> response_body) {
using ash::assistant::metrics::ProactiveSuggestionsRequestResult;
// When the request fails to return a valid response we record the event and
// return a null set of proactive suggestions.
if (!response_body || loader_->NetError() != net::OK ||
!loader_->ResponseInfo() || !loader_->ResponseInfo()->headers) {
ash::assistant::metrics::RecordProactiveSuggestionsRequestResult(
ash::ProactiveSuggestions::kCategoryUnknown,
ProactiveSuggestionsRequestResult::kError);
std::move(complete_callback_).Run(/*proactive_suggestions=*/nullptr);
return;
}
// The proactive suggestions server will return metadata in the HTTP headers.
int category = ash::ProactiveSuggestions::kCategoryUnknown;
std::string description;
std::string search_query;
bool has_content = false;
ParseProactiveSuggestionsMetadata(*loader_->ResponseInfo()->headers,
&category, &description, &search_query,
&has_content);
// When the server indicates that there is no content we record the event and
// return a null set of proactive suggestions.
if (!has_content) {
ash::assistant::metrics::RecordProactiveSuggestionsRequestResult(
category, ProactiveSuggestionsRequestResult::kSuccessWithoutContent);
std::move(complete_callback_).Run(/*proactive_suggestions=*/nullptr);
return;
}
// Otherwise we have a populated proactive suggestions response so we record
// the event and return a fully constructed set of proactive suggestions.
ash::assistant::metrics::RecordProactiveSuggestionsRequestResult(
category, ProactiveSuggestionsRequestResult::kSuccessWithContent);
std::move(complete_callback_)
.Run(base::MakeRefCounted<ash::ProactiveSuggestions>(
category, std::move(description), std::move(search_query),
std::move(*response_body)));
}