blob: cc73904b378fa377151b09253266553b22417210 [file] [log] [blame]
// 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 "components/feed/core/v2/feed_stream.h"
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/feed/core/common/pref_names.h"
#include "components/feed/core/proto/v2/store.pb.h"
#include "components/feed/core/proto/v2/ui.pb.h"
#include "components/feed/core/proto/v2/wire/there_and_back_again_data.pb.h"
#include "components/feed/core/shared_prefs/pref_names.h"
#include "components/feed/core/v2/config.h"
#include "components/feed/core/v2/enums.h"
#include "components/feed/core/v2/feed_network.h"
#include "components/feed/core/v2/feed_store.h"
#include "components/feed/core/v2/image_fetcher.h"
#include "components/feed/core/v2/metrics_reporter.h"
#include "components/feed/core/v2/offline_page_spy.h"
#include "components/feed/core/v2/prefs.h"
#include "components/feed/core/v2/protocol_translator.h"
#include "components/feed/core/v2/refresh_task_scheduler.h"
#include "components/feed/core/v2/scheduling.h"
#include "components/feed/core/v2/stream_model.h"
#include "components/feed/core/v2/surface_updater.h"
#include "components/feed/core/v2/tasks/clear_all_task.h"
#include "components/feed/core/v2/tasks/get_prefetch_suggestions_task.h"
#include "components/feed/core/v2/tasks/load_stream_task.h"
#include "components/feed/core/v2/tasks/prefetch_images_task.h"
#include "components/feed/core/v2/tasks/upload_actions_task.h"
#include "components/feed/core/v2/tasks/wait_for_store_initialize_task.h"
#include "components/feed/feed_feature_list.h"
#include "components/offline_pages/core/prefetch/prefetch_service.h"
#include "components/offline_pages/task/closure_task.h"
#include "components/prefs/pref_service.h"
namespace feed {
namespace {
void UpdateDebugStreamData(
const UploadActionsTask::Result& upload_actions_result,
DebugStreamData& debug_data) {
if (upload_actions_result.last_network_response_info) {
debug_data.upload_info = upload_actions_result.last_network_response_info;
}
}
void PopulateDebugStreamData(const LoadStreamTask::Result& load_result,
PrefService& profile_prefs) {
DebugStreamData debug_data = ::feed::prefs::GetDebugStreamData(profile_prefs);
std::stringstream ss;
ss << "Code: " << load_result.final_status;
debug_data.load_stream_status = ss.str();
debug_data.fetch_info = load_result.network_response_info;
if (load_result.upload_actions_result) {
UpdateDebugStreamData(*load_result.upload_actions_result, debug_data);
}
::feed::prefs::SetDebugStreamData(debug_data, profile_prefs);
}
void PopulateDebugStreamData(
const UploadActionsTask::Result& upload_actions_result,
PrefService& profile_prefs) {
DebugStreamData debug_data = ::feed::prefs::GetDebugStreamData(profile_prefs);
UpdateDebugStreamData(upload_actions_result, debug_data);
::feed::prefs::SetDebugStreamData(debug_data, profile_prefs);
}
} // namespace
// offline_pages::SuggestionsProvider.
class FeedStream::OfflineSuggestionsProvider
: public offline_pages::SuggestionsProvider {
public:
explicit OfflineSuggestionsProvider(FeedStream* stream) : stream_(stream) {}
virtual ~OfflineSuggestionsProvider() = default;
OfflineSuggestionsProvider(const OfflineSuggestionsProvider&) = delete;
OfflineSuggestionsProvider& operator=(const OfflineSuggestionsProvider&) =
delete;
void GetCurrentArticleSuggestions(
SuggestionCallback suggestions_callback) override {
stream_->GetPrefetchSuggestions(std::move(suggestions_callback));
}
// These signals aren't used for v2.
void ReportArticleListViewed() override {}
void ReportArticleViewed(GURL article_url) override {}
private:
FeedStream* stream_;
};
RefreshResponseData FeedStream::WireResponseTranslator::TranslateWireResponse(
feedwire::Response response,
StreamModelUpdateRequest::Source source,
bool was_signed_in_request,
base::Time current_time) const {
return ::feed::TranslateWireResponse(std::move(response), source,
was_signed_in_request, current_time);
}
FeedStream::Metadata::Metadata(FeedStore* store) : store_(store) {}
FeedStream::Metadata::~Metadata() = default;
void FeedStream::Metadata::Populate(feedstore::Metadata metadata) {
metadata_ = std::move(metadata);
}
const std::string& FeedStream::Metadata::GetConsistencyToken() const {
return metadata_.consistency_token();
}
void FeedStream::Metadata::SetConsistencyToken(std::string consistency_token) {
metadata_.set_consistency_token(std::move(consistency_token));
store_->WriteMetadata(metadata_, base::DoNothing());
}
const std::string& FeedStream::Metadata::GetSessionIdToken() const {
return metadata_.session_id().token();
}
base::Time FeedStream::Metadata::GetSessionIdExpiryTime() const {
return base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMilliseconds(
metadata_.session_id().expiry_time_ms()));
}
void FeedStream::Metadata::SetSessionId(std::string token,
base::Time expiry_time) {
feedstore::Metadata::SessionID* session_id = metadata_.mutable_session_id();
session_id->set_token(std::move(token));
session_id->set_expiry_time_ms(
expiry_time.ToDeltaSinceWindowsEpoch().InMilliseconds());
store_->WriteMetadata(metadata_, base::DoNothing());
}
void FeedStream::Metadata::MaybeUpdateSessionId(
base::Optional<std::string> token) {
if (token && metadata_.session_id().token() != *token) {
base::Time expiry_time =
token->empty() ? base::Time()
: base::Time::Now() + GetFeedConfig().session_id_max_age;
SetSessionId(*token, expiry_time);
}
}
LocalActionId FeedStream::Metadata::GetNextActionId() {
uint32_t id = metadata_.next_action_id();
// Never use 0, as that's an invalid LocalActionId.
if (id == 0)
++id;
metadata_.set_next_action_id(id + 1);
store_->WriteMetadata(metadata_, base::DoNothing());
return LocalActionId(id);
}
FeedStream::FeedStream(RefreshTaskScheduler* refresh_task_scheduler,
MetricsReporter* metrics_reporter,
Delegate* delegate,
PrefService* profile_prefs,
FeedNetwork* feed_network,
ImageFetcher* image_fetcher,
FeedStore* feed_store,
PersistentKeyValueStoreImpl* persistent_key_value_store,
offline_pages::PrefetchService* prefetch_service,
offline_pages::OfflinePageModel* offline_page_model,
const ChromeInfo& chrome_info)
: prefetch_service_(prefetch_service),
refresh_task_scheduler_(refresh_task_scheduler),
metrics_reporter_(metrics_reporter),
delegate_(delegate),
profile_prefs_(profile_prefs),
feed_network_(feed_network),
image_fetcher_(image_fetcher),
store_(feed_store),
persistent_key_value_store_(persistent_key_value_store),
chrome_info_(chrome_info),
task_queue_(this),
request_throttler_(profile_prefs),
metadata_(feed_store),
notice_card_tracker_(profile_prefs) {
static WireResponseTranslator default_translator;
wire_response_translator_ = &default_translator;
surface_updater_ = std::make_unique<SurfaceUpdater>(metrics_reporter_);
offline_page_spy_ = std::make_unique<OfflinePageSpy>(surface_updater_.get(),
offline_page_model);
if (prefetch_service_) {
offline_suggestions_provider_ =
std::make_unique<OfflineSuggestionsProvider>(this);
prefetch_service_->SetSuggestionProvider(
offline_suggestions_provider_.get());
}
// Inserting this task first ensures that |store_| is initialized before
// it is used.
task_queue_.AddTask(std::make_unique<WaitForStoreInitializeTask>(this));
UpdateCanUploadActionsWithNoticeCard();
}
void FeedStream::InitializeScheduling() {
if (!IsArticlesListVisible()) {
refresh_task_scheduler_->Cancel();
return;
}
}
FeedStream::~FeedStream() = default;
void FeedStream::TriggerStreamLoad() {
if (model_ || model_loading_in_progress_)
return;
// If we should not load the stream, abort and send a zero-state update.
LoadStreamStatus do_not_attempt_reason = ShouldAttemptLoad();
if (do_not_attempt_reason != LoadStreamStatus::kNoStatus) {
InitialStreamLoadComplete(LoadStreamTask::Result(do_not_attempt_reason));
return;
}
model_loading_in_progress_ = true;
surface_updater_->LoadStreamStarted();
task_queue_.AddTask(std::make_unique<LoadStreamTask>(
LoadStreamTask::LoadType::kInitialLoad, this,
base::BindOnce(&FeedStream::InitialStreamLoadComplete,
base::Unretained(this))));
}
void FeedStream::InitialStreamLoadComplete(LoadStreamTask::Result result) {
PopulateDebugStreamData(result, *profile_prefs_);
metrics_reporter_->OnLoadStream(result.load_from_store_status,
result.final_status,
std::move(result.latencies));
UpdateIsActivityLoggingEnabled();
model_loading_in_progress_ = false;
surface_updater_->LoadStreamComplete(model_ != nullptr, result.final_status);
if (result.loaded_new_content_from_network && prefetch_service_)
prefetch_service_->NewSuggestionsAvailable();
}
void FeedStream::OnEnterBackground() {
UpdateCanUploadActionsWithNoticeCard();
metrics_reporter_->OnEnterBackground();
if (GetFeedConfig().upload_actions_on_enter_background) {
task_queue_.AddTask(std::make_unique<UploadActionsTask>(
this, base::BindOnce(&FeedStream::UploadActionsComplete,
base::Unretained(this))));
}
}
bool FeedStream::IsActivityLoggingEnabled() const {
return is_activity_logging_enabled_ && CanUploadActions();
}
void FeedStream::UpdateIsActivityLoggingEnabled() {
is_activity_logging_enabled_ =
model_ &&
((model_->signed_in() && model_->logging_enabled()) ||
(!model_->signed_in() && GetFeedConfig().send_signed_out_session_logs));
}
std::string FeedStream::GetSessionId() const {
return GetMetadata()->GetSessionIdToken();
}
void FeedStream::PrefetchImage(const GURL& url) {
delegate_->PrefetchImage(url);
}
void FeedStream::AttachSurface(SurfaceInterface* surface) {
metrics_reporter_->SurfaceOpened(surface->GetSurfaceId());
// Skip normal processing when overriding stream data from the internals page.
if (forced_stream_update_for_debugging_.updated_slices_size() > 0) {
surface_updater_->SurfaceAdded(surface);
surface->StreamUpdate(forced_stream_update_for_debugging_);
return;
}
TriggerStreamLoad();
surface_updater_->SurfaceAdded(surface);
// Cancel any scheduled model unload task.
++unload_on_detach_sequence_number_;
UpdateCanUploadActionsWithNoticeCard();
}
void FeedStream::DetachSurface(SurfaceInterface* surface) {
metrics_reporter_->SurfaceClosed(surface->GetSurfaceId());
surface_updater_->SurfaceRemoved(surface);
UpdateCanUploadActionsWithNoticeCard();
ScheduleModelUnloadIfNoSurfacesAttached();
}
void FeedStream::ScheduleModelUnloadIfNoSurfacesAttached() {
if (surface_updater_->HasSurfaceAttached())
return;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FeedStream::AddUnloadModelIfNoSurfacesAttachedTask,
GetWeakPtr(), unload_on_detach_sequence_number_),
GetFeedConfig().model_unload_timeout);
}
void FeedStream::AddUnloadModelIfNoSurfacesAttachedTask(int sequence_number) {
// Don't continue if unload_on_detach_sequence_number_ has changed.
if (unload_on_detach_sequence_number_ != sequence_number)
return;
task_queue_.AddTask(std::make_unique<offline_pages::ClosureTask>(
base::BindOnce(&FeedStream::UnloadModelIfNoSurfacesAttachedTask,
base::Unretained(this))));
}
void FeedStream::UnloadModelIfNoSurfacesAttachedTask() {
if (surface_updater_->HasSurfaceAttached())
return;
UnloadModel();
}
void FeedStream::SetArticlesListVisible(bool is_visible) {
profile_prefs_->SetBoolean(prefs::kArticlesListVisible, is_visible);
}
bool FeedStream::IsArticlesListVisible() {
return profile_prefs_->GetBoolean(prefs::kArticlesListVisible);
}
std::string FeedStream::GetClientInstanceId() const {
return prefs::GetClientInstanceId(*profile_prefs_);
}
bool FeedStream::IsFeedEnabledByEnterprisePolicy() {
return profile_prefs_->GetBoolean(prefs::kEnableSnippets);
}
void FeedStream::LoadMore(SurfaceId surface_id,
base::OnceCallback<void(bool)> callback) {
if (!model_) {
DLOG(ERROR) << "Ignoring LoadMore() before the model is loaded";
return std::move(callback).Run(false);
}
// We want to abort early to avoid showing a loading spinner if it's not
// necessary.
if (ShouldMakeFeedQueryRequest(/*is_load_more=*/true,
/*consume_quota=*/false) !=
LoadStreamStatus::kNoStatus) {
return std::move(callback).Run(false);
}
metrics_reporter_->OnLoadMoreBegin(surface_id);
surface_updater_->SetLoadingMore(true);
// Have at most one in-flight LoadMore() request. Send the result to all
// requestors.
load_more_complete_callbacks_.push_back(std::move(callback));
if (load_more_complete_callbacks_.size() == 1) {
task_queue_.AddTask(std::make_unique<LoadMoreTask>(
this,
base::BindOnce(&FeedStream::LoadMoreComplete, base::Unretained(this))));
}
}
void FeedStream::LoadMoreComplete(LoadMoreTask::Result result) {
UpdateIsActivityLoggingEnabled();
metrics_reporter_->OnLoadMore(result.final_status);
surface_updater_->SetLoadingMore(false);
std::vector<base::OnceCallback<void(bool)>> moved_callbacks =
std::move(load_more_complete_callbacks_);
bool success = result.final_status == LoadStreamStatus::kLoadedFromNetwork;
for (auto& callback : moved_callbacks) {
std::move(callback).Run(success);
}
if (result.loaded_new_content_from_network)
prefetch_service_->NewSuggestionsAvailable();
}
void FeedStream::ExecuteOperations(
std::vector<feedstore::DataOperation> operations) {
if (!model_) {
DLOG(ERROR) << "Calling ExecuteOperations before the model is loaded";
return;
}
return model_->ExecuteOperations(std::move(operations));
}
EphemeralChangeId FeedStream::CreateEphemeralChange(
std::vector<feedstore::DataOperation> operations) {
if (!model_) {
DLOG(ERROR) << "Calling CreateEphemeralChange before the model is loaded";
return {};
}
metrics_reporter_->EphemeralStreamChange();
return model_->CreateEphemeralChange(std::move(operations));
}
EphemeralChangeId FeedStream::CreateEphemeralChangeFromPackedData(
base::StringPiece data) {
feedpacking::DismissData msg;
msg.ParseFromArray(data.data(), data.size());
return CreateEphemeralChange(TranslateDismissData(base::Time::Now(), msg));
}
bool FeedStream::CommitEphemeralChange(EphemeralChangeId id) {
if (!model_)
return false;
return model_->CommitEphemeralChange(id);
}
bool FeedStream::RejectEphemeralChange(EphemeralChangeId id) {
if (!model_)
return false;
metrics_reporter_->EphemeralStreamChangeRejected();
return model_->RejectEphemeralChange(id);
}
void FeedStream::ProcessThereAndBackAgain(base::StringPiece data) {
feedwire::ThereAndBackAgainData msg;
msg.ParseFromArray(data.data(), data.size());
if (msg.has_action_payload()) {
feedwire::FeedAction action_msg;
*action_msg.mutable_action_payload() = std::move(msg.action_payload());
UploadAction(std::move(action_msg), /*upload_now=*/true,
base::BindOnce(&FeedStream::UploadActionsComplete,
base::Unretained(this)));
}
}
void FeedStream::ProcessViewAction(base::StringPiece data) {
if (!CanLogViews()) {
return;
}
feedwire::FeedAction msg;
msg.ParseFromArray(data.data(), data.size());
UploadAction(std::move(msg), /*upload_now=*/false,
base::BindOnce(&FeedStream::UploadActionsComplete,
base::Unretained(this)));
}
void FeedStream::UploadActionsComplete(UploadActionsTask::Result result) {
PopulateDebugStreamData(result, *profile_prefs_);
}
void FeedStream::GetPrefetchSuggestions(
base::OnceCallback<void(std::vector<offline_pages::PrefetchSuggestion>)>
suggestions_callback) {
task_queue_.AddTask(std::make_unique<GetPrefetchSuggestionsTask>(
this, std::move(suggestions_callback)));
}
DebugStreamData FeedStream::GetDebugStreamData() {
return ::feed::prefs::GetDebugStreamData(*profile_prefs_);
}
void FeedStream::ForceRefreshForDebugging() {
// Avoid request throttling for debug refreshes.
feed::prefs::SetThrottlerRequestCounts({}, *profile_prefs_);
task_queue_.AddTask(
std::make_unique<offline_pages::ClosureTask>(base::BindOnce(
&FeedStream::ForceRefreshForDebuggingTask, base::Unretained(this))));
}
void FeedStream::ForceRefreshForDebuggingTask() {
UnloadModel();
store_->ClearStreamData(base::DoNothing());
TriggerStreamLoad();
}
std::string FeedStream::DumpStateForDebugging() {
std::stringstream ss;
if (model_) {
ss << "model loaded, " << model_->GetContentList().size() << " contents, "
<< "signed_in=" << model_->signed_in()
<< ", logging_enabled=" << model_->logging_enabled()
<< ", privacy_notice_fulfilled=" << model_->privacy_notice_fulfilled();
}
RequestSchedule schedule = prefs::GetRequestSchedule(*profile_prefs_);
if (schedule.refresh_offsets.empty()) {
ss << "No request schedule\n";
} else {
ss << "Request schedule reference " << schedule.anchor_time << '\n';
for (base::TimeDelta entry : schedule.refresh_offsets) {
ss << " fetch at " << entry << '\n';
}
}
return ss.str();
}
void FeedStream::SetForcedStreamUpdateForDebugging(
const feedui::StreamUpdate& stream_update) {
forced_stream_update_for_debugging_ = stream_update;
}
base::Time FeedStream::GetLastFetchTime() {
const base::Time fetch_time =
profile_prefs_->GetTime(feed::prefs::kLastFetchAttemptTime);
// Ignore impossible time values.
if (fetch_time > base::Time::Now())
return base::Time();
return fetch_time;
}
bool FeedStream::HasSurfaceAttached() const {
return surface_updater_->HasSurfaceAttached();
}
void FeedStream::LoadModelForTesting(std::unique_ptr<StreamModel> model) {
LoadModel(std::move(model));
}
offline_pages::TaskQueue* FeedStream::GetTaskQueueForTesting() {
return &task_queue_;
}
void FeedStream::OnTaskQueueIsIdle() {
if (idle_callback_)
idle_callback_.Run();
}
void FeedStream::SetIdleCallbackForTesting(
base::RepeatingClosure idle_callback) {
idle_callback_ = idle_callback;
}
void FeedStream::OnStoreChange(StreamModel::StoreUpdate update) {
if (!update.operations.empty()) {
DCHECK(!update.update_request);
store_->WriteOperations(update.sequence_number, update.operations);
} else {
DCHECK(update.update_request);
if (update.overwrite_stream_data) {
DCHECK_EQ(update.sequence_number, 0);
store_->OverwriteStream(std::move(update.update_request),
base::DoNothing());
} else {
store_->SaveStreamUpdate(update.sequence_number,
std::move(update.update_request),
base::DoNothing());
}
}
}
LoadStreamStatus FeedStream::ShouldAttemptLoad(bool model_loading) {
// Don't try to load the model if it's already loaded, or in the process of
// being loaded. Because |ShouldAttemptLoad()| is used both before and during
// the load process, we need to ignore this check when |model_loading| is
// true.
if (model_ || (!model_loading && model_loading_in_progress_))
return LoadStreamStatus::kModelAlreadyLoaded;
if (!IsArticlesListVisible())
return LoadStreamStatus::kLoadNotAllowedArticlesListHidden;
if (!IsFeedEnabledByEnterprisePolicy())
return LoadStreamStatus::kLoadNotAllowedDisabledByEnterprisePolicy;
if (!delegate_->IsEulaAccepted())
return LoadStreamStatus::kLoadNotAllowedEulaNotAccepted;
return LoadStreamStatus::kNoStatus;
}
LoadStreamStatus FeedStream::ShouldMakeFeedQueryRequest(bool is_load_more,
bool consume_quota) {
if (!is_load_more) {
// Time has passed since calling |ShouldAttemptLoad()|, call it again to
// confirm we should still attempt loading.
const LoadStreamStatus should_not_attempt_reason =
ShouldAttemptLoad(/*model_loading=*/true);
if (should_not_attempt_reason != LoadStreamStatus::kNoStatus) {
return should_not_attempt_reason;
}
} else {
// LoadMore requires a next page token.
if (!model_ || model_->GetNextPageToken().empty()) {
return LoadStreamStatus::kCannotLoadMoreNoNextPageToken;
}
}
if (delegate_->IsOffline()) {
return LoadStreamStatus::kCannotLoadFromNetworkOffline;
}
if (consume_quota &&
!request_throttler_.RequestQuota(NetworkRequestType::kFeedQuery)) {
return LoadStreamStatus::kCannotLoadFromNetworkThrottled;
}
return LoadStreamStatus::kNoStatus;
}
bool FeedStream::ShouldForceSignedOutFeedQueryRequest() const {
return base::TimeTicks::Now() < signed_out_refreshes_until_;
}
RequestMetadata FeedStream::GetRequestMetadata(bool is_for_next_page) const {
RequestMetadata result;
result.chrome_info = chrome_info_;
result.display_metrics = delegate_->GetDisplayMetrics();
result.language_tag = delegate_->GetLanguageTag();
result.notice_card_acknowledged =
notice_card_tracker_.HasAcknowledgedNoticeCard();
if (is_for_next_page) {
// If we are continuing an existing feed, use whatever session continuity
// mechanism is currently associated with the stream: client-instance-id
// for signed-in feed, session_id token for signed-out.
DCHECK(model_);
if (model_->signed_in()) {
result.client_instance_id = GetClientInstanceId();
} else {
result.session_id = GetMetadata()->GetSessionIdToken();
}
} else {
// The request is for the first page of the feed. Use client_instance_id
// for signed in requests and session_id token (if any, and not expired)
// for signed-out.
if (delegate_->IsSignedIn() && !ShouldForceSignedOutFeedQueryRequest()) {
result.client_instance_id = GetClientInstanceId();
} else if (!GetMetadata()->GetSessionIdToken().empty() &&
GetMetadata()->GetSessionIdExpiryTime() > base::Time::Now()) {
result.session_id = GetMetadata()->GetSessionIdToken();
}
}
DCHECK(result.session_id.empty() || result.client_instance_id.empty());
return result;
}
void FeedStream::OnEulaAccepted() {
if (surface_updater_->HasSurfaceAttached())
TriggerStreamLoad();
}
void FeedStream::OnAllHistoryDeleted() {
// Give sync the time to propagate the changes in history to the server.
// In the interim, only send signed-out FeedQuery requests.
signed_out_refreshes_until_ =
base::TimeTicks::Now() + kSuppressRefreshDuration;
ClearAll();
}
void FeedStream::OnCacheDataCleared() {
ClearAll();
}
void FeedStream::OnSignedIn() {
// On sign-in, turn off activity logging. This avoids the possibility that we
// send logs with the wrong user info attached, but may cause us to lose
// buffered events.
is_activity_logging_enabled_ = false;
UpdateCanUploadActionsWithNoticeCard();
ClearAll();
}
void FeedStream::OnSignedOut() {
// On sign-out, turn off activity logging. This avoids the possibility that we
// send logs with the wrong user info attached, but may cause us to lose
// buffered events.
is_activity_logging_enabled_ = false;
UpdateCanUploadActionsWithNoticeCard();
ClearAll();
}
void FeedStream::ExecuteRefreshTask() {
// Schedule the next refresh attempt. If a new refresh schedule is returned
// through this refresh, it will be overwritten.
SetRequestSchedule(feed::prefs::GetRequestSchedule(*profile_prefs_));
LoadStreamStatus do_not_attempt_reason = ShouldAttemptLoad();
if (do_not_attempt_reason != LoadStreamStatus::kNoStatus) {
BackgroundRefreshComplete(LoadStreamTask::Result(do_not_attempt_reason));
return;
}
task_queue_.AddTask(std::make_unique<LoadStreamTask>(
LoadStreamTask::LoadType::kBackgroundRefresh, this,
base::BindOnce(&FeedStream::BackgroundRefreshComplete,
base::Unretained(this))));
}
void FeedStream::BackgroundRefreshComplete(LoadStreamTask::Result result) {
metrics_reporter_->OnBackgroundRefresh(result.final_status);
if (result.loaded_new_content_from_network && prefetch_service_)
prefetch_service_->NewSuggestionsAvailable();
// Add prefetch images to task queue without waiting to finish
// since we treat them as best-effort.
task_queue_.AddTask(std::make_unique<PrefetchImagesTask>(this));
refresh_task_scheduler_->RefreshTaskComplete();
}
void FeedStream::ClearAll() {
metrics_reporter_->OnClearAll(base::Time::Now() - GetLastFetchTime());
task_queue_.AddTask(std::make_unique<ClearAllTask>(this));
}
void FeedStream::FinishClearAll() {
prefs::ClearClientInstanceId(*profile_prefs_);
metadata_.Populate(feedstore::Metadata());
delegate_->ClearAll();
}
ImageFetchId FeedStream::FetchImage(
const GURL& url,
base::OnceCallback<void(NetworkResponse)> callback) {
return image_fetcher_->Fetch(url, std::move(callback));
}
PersistentKeyValueStoreImpl* FeedStream::GetPersistentKeyValueStore() {
return persistent_key_value_store_;
}
void FeedStream::CancelImageFetch(ImageFetchId id) {
image_fetcher_->Cancel(id);
}
void FeedStream::UploadAction(
feedwire::FeedAction action,
bool upload_now,
base::OnceCallback<void(UploadActionsTask::Result)> callback) {
if (!delegate_->IsSignedIn()) {
DLOG(WARNING)
<< "Called UploadActions while user is signed-out, dropping upload";
return;
}
task_queue_.AddTask(std::make_unique<UploadActionsTask>(
std::move(action), upload_now, this, std::move(callback)));
}
void FeedStream::LoadModel(std::unique_ptr<StreamModel> model) {
DCHECK(!model_);
model_ = std::move(model);
model_->SetStoreObserver(this);
surface_updater_->SetModel(model_.get());
offline_page_spy_->SetModel(model_.get());
ScheduleModelUnloadIfNoSurfacesAttached();
}
void FeedStream::SetRequestSchedule(RequestSchedule schedule) {
const base::Time now = base::Time::Now();
base::Time run_time = NextScheduledRequestTime(now, &schedule);
if (!run_time.is_null()) {
refresh_task_scheduler_->EnsureScheduled(run_time - now);
} else {
refresh_task_scheduler_->Cancel();
}
feed::prefs::SetRequestSchedule(schedule, *profile_prefs_);
}
void FeedStream::UnloadModel() {
// Note: This should only be called from a running Task, as some tasks assume
// the model remains loaded.
if (!model_)
return;
offline_page_spy_->SetModel(nullptr);
surface_updater_->SetModel(nullptr);
model_.reset();
}
void FeedStream::ReportOpenAction(const std::string& slice_id) {
int index = surface_updater_->GetSliceIndexFromSliceId(slice_id);
if (index < 0)
index = MetricsReporter::kUnknownCardIndex;
metrics_reporter_->OpenAction(index);
notice_card_tracker_.OnOpenAction(index);
}
void FeedStream::ReportOpenVisitComplete(base::TimeDelta visit_time) {
metrics_reporter_->OpenVisitComplete(visit_time);
}
void FeedStream::ReportOpenInNewTabAction(const std::string& slice_id) {
int index = surface_updater_->GetSliceIndexFromSliceId(slice_id);
if (index < 0)
index = MetricsReporter::kUnknownCardIndex;
metrics_reporter_->OpenInNewTabAction(index);
notice_card_tracker_.OnOpenAction(index);
}
void FeedStream::ReportOpenInNewIncognitoTabAction() {
metrics_reporter_->OpenInNewIncognitoTabAction();
}
void FeedStream::ReportSliceViewed(SurfaceId surface_id,
const std::string& slice_id) {
int index = surface_updater_->GetSliceIndexFromSliceId(slice_id);
if (index >= 0) {
UpdateShownSlicesUploadCondition(index);
notice_card_tracker_.OnSliceViewed(index);
metrics_reporter_->ContentSliceViewed(surface_id, index);
}
}
// TODO(crbug/1147237): Rename this method and related members?
bool FeedStream::CanUploadActions() const {
return can_upload_actions_with_notice_card_ ||
!prefs::GetLastFetchHadNoticeCard(*profile_prefs_);
}
void FeedStream::SetLastStreamLoadHadNoticeCard(bool value) {
prefs::SetLastFetchHadNoticeCard(*profile_prefs_, value);
}
bool FeedStream::HasReachedConditionsToUploadActionsWithNoticeCard() {
if (base::FeatureList::IsEnabled(
feed::kInterestFeedV2ClicksAndViewsConditionalUpload)) {
return prefs::GetHasReachedClickAndViewActionsUploadConditions(
*profile_prefs_);
}
// Consider the conditions as already reached to enable uploads when the
// feature is disabled. This will also have the effect of not updating the
// related pref.
return true;
}
void FeedStream::DeclareHasReachedConditionsToUploadActionsWithNoticeCard() {
if (base::FeatureList::IsEnabled(
feed::kInterestFeedV2ClicksAndViewsConditionalUpload)) {
prefs::SetHasReachedClickAndViewActionsUploadConditions(*profile_prefs_,
true);
}
}
void FeedStream::UpdateShownSlicesUploadCondition(int viewed_slice_index) {
constexpr int kShownSlicesThreshold = 2;
DCHECK(model_) << "Model was unloaded while handling a viewed slice";
// Don't take shown slices into consideration when the upload conditions has
// already been reached.
if (HasReachedConditionsToUploadActionsWithNoticeCard())
return;
if (!model_->signed_in())
return;
if (viewed_slice_index + 1 >= kShownSlicesThreshold)
DeclareHasReachedConditionsToUploadActionsWithNoticeCard();
}
bool FeedStream::CanLogViews() const {
return CanUploadActions();
}
void FeedStream::UpdateCanUploadActionsWithNoticeCard() {
can_upload_actions_with_notice_card_ =
HasReachedConditionsToUploadActionsWithNoticeCard();
}
void FeedStream::ReportFeedViewed(SurfaceId surface_id) {
metrics_reporter_->FeedViewed(surface_id);
}
void FeedStream::ReportSendFeedbackAction() {
metrics_reporter_->SendFeedbackAction();
}
void FeedStream::ReportLearnMoreAction() {
metrics_reporter_->LearnMoreAction();
}
void FeedStream::ReportDownloadAction() {
metrics_reporter_->DownloadAction();
}
void FeedStream::ReportNavigationStarted() {
metrics_reporter_->NavigationStarted();
}
void FeedStream::ReportPageLoaded() {
metrics_reporter_->PageLoaded();
}
void FeedStream::ReportRemoveAction() {
metrics_reporter_->RemoveAction();
}
void FeedStream::ReportNotInterestedInAction() {
metrics_reporter_->NotInterestedInAction();
}
void FeedStream::ReportManageInterestsAction() {
metrics_reporter_->ManageInterestsAction();
}
void FeedStream::ReportContextMenuOpened() {
metrics_reporter_->ContextMenuOpened();
}
void FeedStream::ReportStreamScrolled(int distance_dp) {
metrics_reporter_->StreamScrolled(distance_dp);
}
void FeedStream::ReportStreamScrollStart() {
metrics_reporter_->StreamScrollStart();
}
void FeedStream::ReportTurnOnAction() {
metrics_reporter_->TurnOnAction();
}
void FeedStream::ReportTurnOffAction() {
metrics_reporter_->TurnOffAction();
}
} // namespace feed