Commit 5080f072 authored by tby's avatar tby Committed by Commit Bot

[Suggested files] Add ItemSuggest cache

This adds a class containing the core logic for calling the ItemSuggest
API from chrome. It's owned by the DriveZeroStateProvider, and the logic
can be broken down into three steps:

1. Retrieve oauth2 access tokens for the primary user.
2. Make a request to the ItemSuggest API.
3. Parse the resulting json.

Follow-up CLs will do something with the parsed json, and add unit
tests.

Bug: 1034842
Change-Id: If7b21e9e80d4390615b74a6e4dbebe79a21ced79
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2420192Reviewed-by: default avatarRachel Wong <wrong@chromium.org>
Commit-Queue: Tony Yeoman <tby@chromium.org>
Cr-Commit-Position: refs/heads/master@{#809662}
parent cfc2ac80
......@@ -1735,6 +1735,8 @@ static_library("ui") {
"app_list/search/files/drive_zero_state_provider.h",
"app_list/search/files/file_result.cc",
"app_list/search/files/file_result.h",
"app_list/search/files/item_suggest_cache.cc",
"app_list/search/files/item_suggest_cache.h",
"app_list/search/launcher_search/launcher_search_icon_image_loader.cc",
"app_list/search/launcher_search/launcher_search_icon_image_loader.h",
"app_list/search/launcher_search/launcher_search_icon_image_loader_impl.cc",
......
......@@ -20,6 +20,7 @@
#include "chrome/browser/ui/app_list/search/drive_quick_access_chip_result.h"
#include "chrome/browser/ui/app_list/search/drive_quick_access_result.h"
#include "chrome/browser/ui/app_list/search/search_controller.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
......@@ -27,10 +28,12 @@ namespace app_list {
DriveZeroStateProvider::DriveZeroStateProvider(
Profile* profile,
SearchController* search_controller)
SearchController* search_controller,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: profile_(profile),
drive_service_(
drive::DriveIntegrationServiceFactory::GetForProfile(profile)),
item_suggest_cache_(profile, std::move(url_loader_factory)),
suggested_files_enabled_(app_list_features::IsSuggestedFilesEnabled()) {
DCHECK(profile_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......@@ -66,7 +69,7 @@ void DriveZeroStateProvider::Start(const base::string16& query) {
void DriveZeroStateProvider::AppListShown() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
item_suggest_cache_.UpdateCache();
// TODO(crbug.com/1034842): Query ItemSuggest, consider rate-limiting.
}
......
......@@ -15,6 +15,7 @@
#include "base/time/time.h"
#include "chrome/browser/chromeos/drive/drive_integration_service.h"
#include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
#include "chrome/browser/ui/app_list/search/files/item_suggest_cache.h"
#include "chrome/browser/ui/app_list/search/search_provider.h"
class Profile;
......@@ -26,7 +27,10 @@ class SearchController;
class DriveZeroStateProvider : public SearchProvider,
public drive::DriveIntegrationServiceObserver {
public:
DriveZeroStateProvider(Profile* profile, SearchController* search_controller);
DriveZeroStateProvider(
Profile* profile,
SearchController* search_controller,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
~DriveZeroStateProvider() override;
DriveZeroStateProvider(const DriveZeroStateProvider&) = delete;
......@@ -44,6 +48,8 @@ class DriveZeroStateProvider : public SearchProvider,
Profile* const profile_;
drive::DriveIntegrationService* const drive_service_;
ItemSuggestCache item_suggest_cache_;
// Whether the suggested files experiment is enabled.
const bool suggested_files_enabled_;
......@@ -54,13 +60,7 @@ class DriveZeroStateProvider : public SearchProvider,
SEQUENCE_CHECKER(sequence_checker_);
scoped_refptr<base::SequencedTaskRunner> task_runner_;
// Factory for general use.
base::WeakPtrFactory<DriveZeroStateProvider> weak_ptr_factory_{this};
// Factory only for weak pointers for ItemSuggest API calls. Using two
// factories allows in-flight API calls to be cancelled independently of other
// tasks by invalidating only this factory's weak pointers.
base::WeakPtrFactory<DriveZeroStateProvider> item_suggest_weak_ptr_factory_{
this};
base::WeakPtrFactory<DriveZeroStateProvider> weak_factory_{this};
};
} // namespace app_list
......
// Copyright 2020 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/app_list/search/files/item_suggest_cache.h"
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "components/google/core/common/google_util.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/consent_level.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace app_list {
namespace {
// Maximum accepted size of an ItemSuggest response. 10 KB.
constexpr int kMaxResponseSize = 10 * 1024;
// TODO(crbug.com/1034842): Investigate:
// - enterprise policies that should limit this traffic.
// - settings that should disable drive results.
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("launcher_item_suggest", R"(
semantics {
sender: "Launcher suggested drive files"
description:
"The Chrome OS launcher requests suggestions for Drive files from "
"the Drive ItemSuggest API. These are displayed in the launcher."
trigger:
"Once on login after Drive FS is mounted. Afterwards, whenever the "
"Chrome OS launcher is opened."
data:
"OAuth2 access token."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"This cannot be disabled."
})");
// The scope required for an access token in order to query ItemSuggest.
constexpr char kDriveScope[] = "https://www.googleapis.com/auth/drive.readonly";
// TODO(crbug.com/1034842): Check this is correct. Also consider:
// - controlling at least the scenario type by experiment param.
// - whether we can filter the response to certain fields
constexpr char kRequestBody[] = R"({
'max_suggestions': 5,
'client_info': {
'platform_type': 'CHROMEOS',
'application_type': 'GOOGLE_DRIVE',
'scenario_type': 'QUICK_ACCESS'
}})";
//----------------
// Error utilities
//----------------
// Possible error states of the item suggest cache. These values persist to
// logs. Entries should not be renumbered and numeric values should never be
// reused.
enum class Error {
kDisabled = 1,
kInvalidServerUrl = 2,
kNoIdentityManager = 3,
kGoogleAuthError = 4,
kNetError = 5,
k3xxError = 6,
k4xxError = 7,
k5xxError = 8,
kEmptyResponse = 9,
kNoResultsInResponse = 10,
kJsonParseFailure = 11,
kJsonConversionFailure = 12,
kMaxValue = kJsonConversionFailure,
};
void LogError(Error error) {
// TODO(crbug.com/1034842): Implement.
}
void LogResponseSize(const int size) {
// TODO(crbug.com/1034842): Implement.
}
} // namespace
// static
const base::Feature ItemSuggestCache::kExperiment{
"LauncherItemSuggest", base::FEATURE_DISABLED_BY_DEFAULT};
constexpr base::FeatureParam<bool> ItemSuggestCache::kEnabled;
constexpr base::FeatureParam<std::string> ItemSuggestCache::kServerUrl;
ItemSuggestCache::ItemSuggestCache(
Profile* profile,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: enabled_(kEnabled.Get()),
server_url_(kServerUrl.Get()),
profile_(profile),
url_loader_factory_(std::move(url_loader_factory)) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
ItemSuggestCache::~ItemSuggestCache() = default;
void ItemSuggestCache::UpdateCache() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(crbug.com/1034842): Add rate-limiting for cache updates.
// Make no requests and exit in four cases:
// - item suggest has been disabled via experiment
// - the server url is not https
// - the server url is not trusted by Google
// - another request is in-flight (url_loader_ is non-null)
if (url_loader_) {
return;
} else if (!enabled_) {
LogError(Error::kDisabled);
return;
} else if (!server_url_.SchemeIs(url::kHttpsScheme) ||
!google_util::IsGoogleAssociatedDomainUrl(server_url_)) {
LogError(Error::kInvalidServerUrl);
return;
}
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile_);
if (!identity_manager) {
LogError(Error::kNoIdentityManager);
return;
}
signin::ScopeSet scopes({kDriveScope});
// Fetch an OAuth2 access token.
token_fetcher_ = std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
"launcher_item_suggest", identity_manager, scopes,
base::BindOnce(&ItemSuggestCache::OnTokenReceived,
weak_factory_.GetWeakPtr()),
signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate,
signin::ConsentLevel::kSync);
}
void ItemSuggestCache::OnTokenReceived(GoogleServiceAuthError error,
signin::AccessTokenInfo token_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
token_fetcher_.reset();
if (error.state() != GoogleServiceAuthError::NONE) {
LogError(Error::kGoogleAuthError);
return;
}
// Make a new request.
url_loader_ = MakeRequestLoader(token_info.token);
url_loader_->SetRetryOptions(0, network::SimpleURLLoader::RETRY_NEVER);
url_loader_->AttachStringForUpload(kRequestBody, "application/json");
// Perform the request.
url_loader_->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&ItemSuggestCache::OnSuggestionsReceived,
weak_factory_.GetWeakPtr()),
kMaxResponseSize);
}
void ItemSuggestCache::OnSuggestionsReceived(
const std::unique_ptr<std::string> json_response) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const int net_error = url_loader_->NetError();
if (net_error != net::OK) {
if (!url_loader_->ResponseInfo() || !url_loader_->ResponseInfo()->headers) {
LogError(Error::kNetError);
} else {
const int status = url_loader_->ResponseInfo()->headers->response_code();
if (status >= 500) {
LogError(Error::k5xxError);
} else if (status >= 400) {
LogError(Error::k4xxError);
} else if (status >= 300) {
LogError(Error::k3xxError);
}
}
return;
} else if (!json_response || json_response->empty()) {
LogError(Error::kEmptyResponse);
return;
}
LogResponseSize(json_response->size());
// Parse the JSON response from ItemSuggest.
data_decoder::DataDecoder::ParseJsonIsolated(
*json_response, base::BindOnce(&ItemSuggestCache::OnJsonParsed,
weak_factory_.GetWeakPtr()));
}
void ItemSuggestCache::OnJsonParsed(
data_decoder::DataDecoder::ValueOrError result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!result.value) {
LogError(Error::kJsonParseFailure);
return;
}
// TODO(crbug.com/1034842): Convert json to result objects.
}
std::unique_ptr<network::SimpleURLLoader> ItemSuggestCache::MakeRequestLoader(
const std::string& token) {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->method = "POST";
resource_request->url = server_url_;
// Do not allow cookies.
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
// Ignore the cache because we always want fresh results.
resource_request->load_flags =
net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
DCHECK(resource_request->url.is_valid());
resource_request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
"application/json");
resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
"Bearer " + token);
return network::SimpleURLLoader::Create(std::move(resource_request),
kTrafficAnnotation);
}
} // namespace app_list
// Copyright 2020 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.
#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_FILES_ITEM_SUGGEST_CACHE_H_
#define CHROME_BROWSER_UI_APP_LIST_SEARCH_FILES_ITEM_SUGGEST_CACHE_H_
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/sequence_checker.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
class Profile;
namespace network {
class SharedURLLoaderFactory;
class SimpleURLLoader;
} // namespace network
namespace app_list {
class ItemSuggestCache {
public:
ItemSuggestCache(
Profile* profile,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
~ItemSuggestCache();
ItemSuggestCache(const ItemSuggestCache&) = delete;
ItemSuggestCache& operator=(const ItemSuggestCache&) = delete;
void UpdateCache();
// Whether or not to override configuration of the cache with an experiment.
static const base::Feature kExperiment;
private:
// Whether or not the ItemSuggestCache is enabled.
static constexpr base::FeatureParam<bool> kEnabled{&kExperiment, "enabled",
true};
// The url of the service that fetches descriptions given image pixels.
static constexpr base::FeatureParam<std::string> kServerUrl{
&kExperiment, "server_url",
"https://appsitemsuggest-pa.googleapis.com/v1/items"};
void OnTokenReceived(GoogleServiceAuthError error,
signin::AccessTokenInfo token_info);
void OnSuggestionsReceived(const std::unique_ptr<std::string> json_response);
void OnJsonParsed(data_decoder::DataDecoder::ValueOrError result);
std::unique_ptr<network::SimpleURLLoader> MakeRequestLoader(
const std::string& token);
const bool enabled_;
const GURL server_url_;
Profile* profile_;
std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher> token_fetcher_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
std::unique_ptr<network::SimpleURLLoader> url_loader_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<ItemSuggestCache> weak_factory_{this};
};
} // namespace app_list
#endif // CHROME_BROWSER_UI_APP_LIST_SEARCH_FILES_ITEM_SUGGEST_CACHE_H_
......@@ -25,6 +25,7 @@
#include "chrome/browser/ui/app_list/search/assistant_search_provider.h"
#include "chrome/browser/ui/app_list/search/assistant_text_search_provider.h"
#include "chrome/browser/ui/app_list/search/drive_quick_access_provider.h"
#include "chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h"
#include "chrome/browser/ui/app_list/search/launcher_search/launcher_search_provider.h"
#include "chrome/browser/ui/app_list/search/mixer.h"
#include "chrome/browser/ui/app_list/search/omnibox_provider.h"
......@@ -36,6 +37,8 @@
#include "chrome/common/chrome_switches.h"
#include "chromeos/services/assistant/public/cpp/features.h"
#include "components/arc/arc_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
namespace app_list {
......@@ -83,6 +86,13 @@ constexpr size_t kMaxAssistantTextResults = 1;
// TODO(wutao): Need UX spec.
constexpr size_t kMaxSettingsShortcutResults = 6;
// A flag to easily replace the old Drive zero-state provider with the new one
// during development.
//
// TODO(crbug.com/1034842): Once implementation is finished, remove this flag
// and always use the new provider.
constexpr bool kUseNewDriveProvider = false;
} // namespace
std::unique_ptr<SearchController> CreateSearchController(
......@@ -195,9 +205,19 @@ std::unique_ptr<SearchController> CreateSearchController(
std::make_unique<ZeroStateFileProvider>(profile));
size_t drive_quick_access_group_id =
controller->AddGroup(kMaxDriveQuickAccessResults);
controller->AddProvider(
drive_quick_access_group_id,
std::make_unique<DriveQuickAccessProvider>(profile, controller.get()));
if (kUseNewDriveProvider) {
controller->AddProvider(
drive_quick_access_group_id,
std::make_unique<DriveZeroStateProvider>(
profile, controller.get(),
content::BrowserContext::GetDefaultStoragePartition(profile)
->GetURLLoaderFactoryForBrowserProcess()));
} else {
controller->AddProvider(drive_quick_access_group_id,
std::make_unique<DriveQuickAccessProvider>(
profile, controller.get()));
}
}
if (app_list_features::IsLauncherSettingsSearchEnabled()) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment