blob: a3c815a1104f456b03350ebe13165caa18f8823e [file] [log] [blame]
// Copyright 2016 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/ntp_snippets/content_suggestions_service_factory.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/memory/singleton.h"
#include "base/time/default_clock.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/favicon/large_icon_service_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/image_fetcher/image_decoder_impl.h"
#include "chrome/browser/language/url_language_histogram_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "components/image_fetcher/core/image_decoder.h"
#include "components/image_fetcher/core/image_fetcher.h"
#include "components/image_fetcher/core/image_fetcher_impl.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/language/core/browser/url_language_histogram.h"
#include "components/leveldb_proto/content/proto_database_provider_factory.h"
#include "components/ntp_snippets/category_rankers/category_ranker.h"
#include "components/ntp_snippets/content_suggestions_service.h"
#include "components/ntp_snippets/features.h"
#include "components/ntp_snippets/logger.h"
#include "components/ntp_snippets/ntp_snippets_constants.h"
#include "components/ntp_snippets/remote/persistent_scheduler.h"
#include "components/ntp_snippets/remote/prefetched_pages_tracker.h"
#include "components/ntp_snippets/remote/remote_suggestions_database.h"
#include "components/ntp_snippets/remote/remote_suggestions_fetcher_impl.h"
#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
#include "components/ntp_snippets/remote/remote_suggestions_scheduler_impl.h"
#include "components/ntp_snippets/remote/remote_suggestions_status_service_impl.h"
#include "components/ntp_snippets/user_classifier.h"
#include "components/offline_pages/buildflags/buildflags.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/service_manager_connection.h"
#include "google_apis/google_api_keys.h"
#include "net/url_request/url_request_context_getter.h"
#include "services/data_decoder/public/cpp/safe_json_parser.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#if defined(OS_ANDROID)
#include "chrome/browser/android/chrome_feature_list.h"
#include "chrome/browser/android/ntp/ntp_snippets_launcher.h"
#include "components/feed/feed_feature_list.h"
#endif
#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
#include "chrome/browser/offline_pages/offline_page_model_factory.h"
#include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h"
#include "chrome/browser/offline_pages/request_coordinator_factory.h"
#include "components/ntp_snippets/remote/prefetched_pages_tracker_impl.h"
#include "components/offline_pages/core/background/request_coordinator.h"
#include "components/offline_pages/core/offline_page_feature.h"
#include "components/offline_pages/core/offline_page_model.h"
#include "components/offline_pages/core/prefetch/prefetch_service.h"
#include "components/offline_pages/core/prefetch/suggested_articles_observer.h"
#endif
using content::BrowserThread;
using history::HistoryService;
using image_fetcher::ImageFetcherImpl;
using language::UrlLanguageHistogram;
using ntp_snippets::CategoryRanker;
using ntp_snippets::ContentSuggestionsService;
using ntp_snippets::GetFetchEndpoint;
using ntp_snippets::PersistentScheduler;
using ntp_snippets::PrefetchedPagesTracker;
using ntp_snippets::RemoteSuggestionsDatabase;
using ntp_snippets::RemoteSuggestionsFetcherImpl;
using ntp_snippets::RemoteSuggestionsProviderImpl;
using ntp_snippets::RemoteSuggestionsSchedulerImpl;
using ntp_snippets::RemoteSuggestionsStatusServiceImpl;
using ntp_snippets::UserClassifier;
#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
using ntp_snippets::PrefetchedPagesTrackerImpl;
using offline_pages::OfflinePageModel;
using offline_pages::OfflinePageModelFactory;
using offline_pages::RequestCoordinator;
using offline_pages::RequestCoordinatorFactory;
#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
// For now, ContentSuggestionsService must only be instantiated on Android.
// See also crbug.com/688366.
#if defined(OS_ANDROID)
#define CONTENT_SUGGESTIONS_ENABLED 1
#else
#define CONTENT_SUGGESTIONS_ENABLED 0
#endif // OS_ANDROID
// The actual #if that does the work is below in BuildServiceInstanceFor. This
// one is just required to avoid "unused code" compiler errors.
#if CONTENT_SUGGESTIONS_ENABLED
namespace {
#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
void RegisterWithPrefetching(ContentSuggestionsService* service,
Profile* profile) {
// There's a circular dependency between ContentSuggestionsService and
// PrefetchService. This closes the circle.
offline_pages::PrefetchServiceFactory::GetForBrowserContext(profile)
->SetContentSuggestionsService(service);
}
#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
bool IsArticleProviderEnabled() {
return base::FeatureList::IsEnabled(ntp_snippets::kArticleSuggestionsFeature);
}
bool IsKeepingPrefetchedSuggestionsEnabled() {
return base::FeatureList::IsEnabled(
ntp_snippets::kKeepPrefetchedContentSuggestions);
}
void RegisterArticleProviderIfEnabled(ContentSuggestionsService* service,
Profile* profile,
UserClassifier* user_classifier,
OfflinePageModel* offline_page_model,
ntp_snippets::Logger* debug_logger) {
if (!IsArticleProviderEnabled()) {
return;
}
PrefService* pref_service = profile->GetPrefs();
identity::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
UrlLanguageHistogram* language_histogram =
UrlLanguageHistogramFactory::GetForBrowserContext(profile);
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
content::BrowserContext::GetDefaultStoragePartition(profile)
->GetURLLoaderFactoryForBrowserProcess();
base::FilePath database_dir(
profile->GetPath().Append(ntp_snippets::kDatabaseFolder));
std::string api_key;
// The API is private. If we don't have the official API key, don't even try.
if (google_apis::IsGoogleChromeAPIKeyUsed()) {
bool is_stable_channel =
chrome::GetChannel() == version_info::Channel::STABLE;
api_key = is_stable_channel ? google_apis::GetAPIKey()
: google_apis::GetNonStableAPIKey();
}
std::unique_ptr<PrefetchedPagesTracker> prefetched_pages_tracker;
#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
if (IsKeepingPrefetchedSuggestionsEnabled()) {
prefetched_pages_tracker =
std::make_unique<PrefetchedPagesTrackerImpl>(offline_page_model);
}
#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
auto suggestions_fetcher = std::make_unique<RemoteSuggestionsFetcherImpl>(
identity_manager, url_loader_factory, pref_service, language_histogram,
base::Bind(
&data_decoder::SafeJsonParser::Parse,
content::ServiceManagerConnection::GetForProcess()->GetConnector()),
GetFetchEndpoint(), api_key, user_classifier);
auto provider = std::make_unique<RemoteSuggestionsProviderImpl>(
service, pref_service, g_browser_process->GetApplicationLocale(),
service->category_ranker(), service->remote_suggestions_scheduler(),
std::move(suggestions_fetcher),
std::make_unique<ImageFetcherImpl>(std::make_unique<ImageDecoderImpl>(),
url_loader_factory),
std::make_unique<RemoteSuggestionsDatabase>(
leveldb_proto::ProtoDatabaseProviderFactory::GetForKey(
profile->GetProfileKey()),
database_dir),
std::make_unique<RemoteSuggestionsStatusServiceImpl>(
identity_manager->HasPrimaryAccount(), pref_service, std::string()),
std::move(prefetched_pages_tracker), debug_logger,
std::make_unique<base::OneShotTimer>());
service->remote_suggestions_scheduler()->SetProvider(provider.get());
service->set_remote_suggestions_provider(provider.get());
service->RegisterProvider(std::move(provider));
}
} // namespace
#endif // CONTENT_SUGGESTIONS_ENABLED
// static
ContentSuggestionsServiceFactory*
ContentSuggestionsServiceFactory::GetInstance() {
return base::Singleton<ContentSuggestionsServiceFactory>::get();
}
// static
ContentSuggestionsService* ContentSuggestionsServiceFactory::GetForProfile(
Profile* profile) {
return static_cast<ContentSuggestionsService*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
// static
ContentSuggestionsService*
ContentSuggestionsServiceFactory::GetForProfileIfExists(Profile* profile) {
return static_cast<ContentSuggestionsService*>(
GetInstance()->GetServiceForBrowserContext(profile, false));
}
ContentSuggestionsServiceFactory::ContentSuggestionsServiceFactory()
: BrowserContextKeyedServiceFactory(
"ContentSuggestionsService",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(HistoryServiceFactory::GetInstance());
DependsOn(IdentityManagerFactory::GetInstance());
DependsOn(LargeIconServiceFactory::GetInstance());
DependsOn(leveldb_proto::ProtoDatabaseProviderFactory::GetInstance());
#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
// Depends on OfflinePageModelFactory in SimpleDependencyManager.
DependsOn(offline_pages::PrefetchServiceFactory::GetInstance());
#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
}
ContentSuggestionsServiceFactory::~ContentSuggestionsServiceFactory() = default;
KeyedService* ContentSuggestionsServiceFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
#if defined(OS_ANDROID)
if (base::FeatureList::IsEnabled(feed::kInterestFeedContentSuggestions)) {
return nullptr;
}
#endif // defined(OS_ANDROID)
#if CONTENT_SUGGESTIONS_ENABLED
using State = ContentSuggestionsService::State;
Profile* profile = Profile::FromBrowserContext(context);
DCHECK(!profile->IsOffTheRecord());
PrefService* pref_service = profile->GetPrefs();
auto user_classifier = std::make_unique<UserClassifier>(
pref_service, base::DefaultClock::GetInstance());
auto* user_classifier_raw = user_classifier.get();
#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
OfflinePageModel* offline_page_model =
OfflinePageModelFactory::GetForBrowserContext(profile);
#else
OfflinePageModel* offline_page_model = nullptr;
#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
auto debug_logger = std::make_unique<ntp_snippets::Logger>();
ntp_snippets::Logger* raw_debug_logger = debug_logger.get();
// Create the RemoteSuggestionsScheduler.
PersistentScheduler* persistent_scheduler = nullptr;
#if defined(OS_ANDROID)
persistent_scheduler = NTPSnippetsLauncher::Get();
#endif // OS_ANDROID
auto scheduler = std::make_unique<RemoteSuggestionsSchedulerImpl>(
persistent_scheduler, user_classifier_raw, pref_service,
g_browser_process->local_state(), base::DefaultClock::GetInstance(),
raw_debug_logger);
// Create the ContentSuggestionsService.
identity::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
HistoryService* history_service = HistoryServiceFactory::GetForProfile(
profile, ServiceAccessType::EXPLICIT_ACCESS);
favicon::LargeIconService* large_icon_service =
LargeIconServiceFactory::GetForBrowserContext(profile);
std::unique_ptr<CategoryRanker> category_ranker =
ntp_snippets::BuildSelectedCategoryRanker(
pref_service, base::DefaultClock::GetInstance());
auto* service = new ContentSuggestionsService(
State::ENABLED, identity_manager, history_service, large_icon_service,
pref_service, std::move(category_ranker), std::move(user_classifier),
std::move(scheduler), std::move(debug_logger));
RegisterArticleProviderIfEnabled(service, profile, user_classifier_raw,
offline_page_model, raw_debug_logger);
#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
RegisterWithPrefetching(service, profile);
#endif
return service;
#else
return nullptr;
#endif // CONTENT_SUGGESTIONS_ENABLED
}