[Merge M87] Feature to conditionally enable logging in Feed V2

(cherry picked from commit f69841348e2a1c1ff4836048385a72150fc7684a)

Bug: 1137414
Change-Id: Ibfc667c96b595292d8118bf5e46455221ad8c274
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2424839
Commit-Queue: Vincent Boisselle <[email protected]>
Reviewed-by: Dan H <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#815788}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2468636
Reviewed-by: Vincent Boisselle <[email protected]>
Cr-Commit-Position: refs/branch-heads/4280@{#324}
Cr-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852}
diff --git a/components/feed/core/v2/enums.cc b/components/feed/core/v2/enums.cc
index 283dad2..b502b1b1 100644
--- a/components/feed/core/v2/enums.cc
+++ b/components/feed/core/v2/enums.cc
@@ -78,6 +78,8 @@
       return out << "kFinishedWithoutUpdatingConsistencyToken";
     case UploadActionsStatus::kAbortUploadForSignedOutUser:
       return out << "kAbortUploadForSignedOutUser";
+    case UploadActionsStatus::kAbortUploadBecauseDisabled:
+      return out << "kAbortUploadBecauseDisabled";
   }
 #else
   return out << (static_cast<int>(value));
diff --git a/components/feed/core/v2/enums.h b/components/feed/core/v2/enums.h
index f9beb339..3a394525 100644
--- a/components/feed/core/v2/enums.h
+++ b/components/feed/core/v2/enums.h
@@ -55,7 +55,8 @@
   kUpdatedConsistencyToken = 4,
   kFinishedWithoutUpdatingConsistencyToken = 5,
   kAbortUploadForSignedOutUser = 6,
-  kMaxValue = kAbortUploadForSignedOutUser,
+  kAbortUploadBecauseDisabled = 7,
+  kMaxValue = kAbortUploadBecauseDisabled,
 };
 
 // Keep this in sync with FeedUploadActionsBatchStatus in enums.xml.
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index 7bd5bc07..022579b 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -36,6 +36,7 @@
 #include "components/feed/core/v2/tasks/load_stream_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"
@@ -174,6 +175,8 @@
   // 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() {
@@ -220,6 +223,7 @@
 }
 
 void FeedStream::OnEnterBackground() {
+  UpdateCanUploadActionsWithNoticeCard();
   metrics_reporter_->OnEnterBackground();
   if (GetFeedConfig().upload_actions_on_enter_background) {
     task_queue_.AddTask(std::make_unique<UploadActionsTask>(
@@ -229,7 +233,7 @@
 }
 
 bool FeedStream::IsActivityLoggingEnabled() const {
-  return is_activity_logging_enabled_;
+  return is_activity_logging_enabled_ && CanUploadActions();
 }
 
 void FeedStream::UpdateIsActivityLoggingEnabled() {
@@ -243,11 +247,13 @@
   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();
 }
 
@@ -388,6 +394,10 @@
 }
 
 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,
@@ -578,6 +588,8 @@
   // buffered events.
   is_activity_logging_enabled_ = false;
 
+  UpdateCanUploadActionsWithNoticeCard();
+
   ClearAll();
 }
 
@@ -587,6 +599,8 @@
   // buffered events.
   is_activity_logging_enabled_ = false;
 
+  UpdateCanUploadActionsWithNoticeCard();
+
   ClearAll();
 }
 
@@ -699,8 +713,53 @@
 void FeedStream::ReportSliceViewed(SurfaceId surface_id,
                                    const std::string& slice_id) {
   int index = surface_updater_->GetSliceIndexFromSliceId(slice_id);
-  if (index >= 0)
+  if (index >= 0) {
+    UpdateShownSlicesUploadCondition(index);
     metrics_reporter_->ContentSliceViewed(surface_id, index);
+  }
+}
+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;
+
+  // Don't take shown slices into consideration when the upload conditions has
+  // already been reached.
+  if (HasReachedConditionsToUploadActionsWithNoticeCard())
+    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);
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h
index b3c2690..0b3082a 100644
--- a/components/feed/core/v2/feed_stream.h
+++ b/components/feed/core/v2/feed_stream.h
@@ -269,6 +269,9 @@
   }
   void SetIdleCallbackForTesting(base::RepeatingClosure idle_callback);
 
+  bool CanUploadActions() const;
+  void SetLastStreamLoadHadNoticeCard(bool value);
+
  private:
   class OfflineSuggestionsProvider;
 
@@ -300,6 +303,15 @@
 
   bool IsFeedEnabledByEnterprisePolicy();
 
+  bool HasReachedConditionsToUploadActionsWithNoticeCard();
+  void DeclareHasReachedConditionsToUploadActionsWithNoticeCard();
+
+  void UpdateShownSlicesUploadCondition(int index);
+
+  bool CanLogViews() const;
+
+  void UpdateCanUploadActionsWithNoticeCard();
+
   // Unowned.
 
   offline_pages::PrefetchService* prefetch_service_;
@@ -335,6 +347,9 @@
   Metadata metadata_;
   int unload_on_detach_sequence_number_ = 0;
   bool is_activity_logging_enabled_ = false;
+  // Whether the feed stream can upload actions with the notice card in the
+  // feed.
+  bool can_upload_actions_with_notice_card_ = false;
 
   // To allow tests to wait on task queue idle.
   base::RepeatingClosure idle_callback_;
diff --git a/components/feed/core/v2/feed_stream_unittest.cc b/components/feed/core/v2/feed_stream_unittest.cc
index 96fc8a13..89e006b8 100644
--- a/components/feed/core/v2/feed_stream_unittest.cc
+++ b/components/feed/core/v2/feed_stream_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/strings/string_util.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_run_loop_timeout.h"
 #include "base/test/simple_test_clock.h"
 #include "base/test/simple_test_tick_clock.h"
@@ -45,6 +46,7 @@
 #include "components/feed/core/v2/test/callback_receiver.h"
 #include "components/feed/core/v2/test/proto_printer.h"
 #include "components/feed/core/v2/test/stream_builder.h"
+#include "components/feed/feed_feature_list.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
 #include "components/offline_pages/core/client_namespace_constants.h"
 #include "components/offline_pages/core/page_criteria.h"
@@ -132,6 +134,12 @@
   return badge_serialized;
 }
 
+feedwire::ThereAndBackAgainData MakeThereAndBackAgainData(int64_t id) {
+  feedwire::ThereAndBackAgainData msg;
+  *msg.mutable_action_payload() = MakeFeedAction(id).action_payload();
+  return msg;
+}
+
 // This is EXPECT_EQ, but also dumps the string values for ease of reading.
 #define EXPECT_STRINGS_EQUAL(WANT, GOT)                                       \
   {                                                                           \
@@ -545,6 +553,8 @@
 class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
  public:
   void SetUp() override {
+    SetupFeatures();
+
     // Reset to default config, since tests can change it.
     SetFeedConfigForTesting(Config());
 
@@ -563,6 +573,11 @@
     CreateStream();
   }
 
+  virtual void SetupFeatures() {
+    scoped_feature_list_.InitAndDisableFeature(
+        feed::kInterestFeedV2ClicksAndViewsConditionalUpload);
+  }
+
   void TearDown() override {
     // Ensure the task queue can return to idle. Failure to do so may be due
     // to a stuck task that never called |TaskComplete()|.
@@ -674,6 +689,14 @@
   bool is_eula_accepted_ = true;
   bool is_offline_ = false;
   bool is_signed_in_ = true;
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+class FeedStreamConditionalActionsUploadTest : public FeedStreamTest {
+  void SetupFeatures() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        feed::kInterestFeedV2ClicksAndViewsConditionalUpload);
+  }
 };
 
 TEST_F(FeedStreamTest, IsArticlesListVisibleByDefault) {
@@ -1571,6 +1594,191 @@
   EXPECT_EQ(1, network_.action_request_call_count);
 }
 
+TEST_F(FeedStreamTest, ActionsUploadWithoutConditionsWhenFeatureDisabled) {
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+  stream_->ProcessViewAction(
+      feedwire::FeedAction::default_instance().SerializeAsString());
+  WaitForIdleTaskQueue();
+  stream_->ProcessThereAndBackAgain(
+      MakeThereAndBackAgainData(42ul).SerializeAsString());
+  WaitForIdleTaskQueue();
+
+  // Verify the actions were uploaded.
+  ASSERT_EQ(1, network_.action_request_call_count);
+  EXPECT_EQ(2, network_.action_request_sent->feed_actions_size());
+}
+
+TEST_F(FeedStreamConditionalActionsUploadTest,
+       NoActionsUploadUntilReachedConditions) {
+  // Tests the flow where we:
+  //   (1) Perform a ThereAndBackAgain action and a View action while upload
+  //   isn't enabled => (2) Attempt an upload while the upload conditions aren't
+  //   reached => (3) Reach upload conditions => (4) Perform another View action
+  //   that should be dropped => (5) Simulate the backgrounding of the app to
+  //   enable actions upload => (6) Trigger an upload which will upload the
+  //   stored ThereAndBackAgain action.
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  // Process the view action and the ThereAndBackAgain action while the upload
+  // conditions aren't reached.
+  stream_->ProcessViewAction(MakeFeedAction(42ul).SerializeAsString());
+  WaitForIdleTaskQueue();
+  // Verify that the view action was dropped.
+  ASSERT_EQ(0ul, ReadStoredActions(stream_->GetStore()).size());
+
+  stream_->ProcessThereAndBackAgain(
+      MakeThereAndBackAgainData(42ul).SerializeAsString());
+  WaitForIdleTaskQueue();
+  // Verify that the ThereAndBackAgain action is in the action store.
+  ASSERT_EQ(1ul, ReadStoredActions(stream_->GetStore()).size());
+
+  // Attempt an upload.
+  stream_->OnEnterBackground();
+  WaitForIdleTaskQueue();
+  // Verify that no upload is done because the conditions aren't reached.
+  EXPECT_EQ(0, network_.action_request_call_count);
+
+  // Reach conditions.
+  stream_->ReportSliceViewed(
+      surface.GetSurfaceId(),
+      surface.initial_state->updated_slices(1).slice().slice_id());
+
+  // Verify that the view action is still dropped because we haven't
+  // transitioned out of the current surface.
+  stream_->ProcessViewAction(MakeFeedAction(42ul).SerializeAsString());
+  WaitForIdleTaskQueue();
+  ASSERT_EQ(1ul, ReadStoredActions(stream_->GetStore()).size());
+
+  // Enable the upload bit and trigger the upload of actions.
+  stream_->OnEnterBackground();
+  WaitForIdleTaskQueue();
+
+  // Verify that the ThereAndBackAgain action was uploaded but not the view
+  // action.
+  ASSERT_EQ(1, network_.action_request_call_count);
+  EXPECT_EQ(1, network_.action_request_sent->feed_actions_size());
+}
+
+TEST_F(FeedStreamConditionalActionsUploadTest, EnableUploadOnSurfaceAttached) {
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  // Perform a ThereAndBackAgain action.
+  stream_->ProcessThereAndBackAgain(
+      MakeThereAndBackAgainData(42ul).SerializeAsString());
+  WaitForIdleTaskQueue();
+
+  // Reach conditions.
+  stream_->ReportSliceViewed(
+      surface.GetSurfaceId(),
+      surface.initial_state->updated_slices(1).slice().slice_id());
+
+  // Attach a new surface to update the bit to enable uploads.
+  TestSurface surface2(stream_.get());
+
+  // Trigger an upload through load more to isolate the effect of the on-attach
+  // event on enabling uploads.
+  response_translator_.InjectResponse(MakeTypicalNextPageState());
+  stream_->LoadMore(surface.GetSurfaceId(), base::DoNothing());
+  WaitForIdleTaskQueue();
+
+  // Verify that the ThereAndBackAgain action was uploaded.
+  ASSERT_EQ(1, network_.action_request_call_count);
+  EXPECT_EQ(1, network_.action_request_sent->feed_actions_size());
+}
+
+TEST_F(FeedStreamConditionalActionsUploadTest, EnableUploadOnEnterBackground) {
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  // Perform a ThereAndBackAgain action.
+  stream_->ProcessThereAndBackAgain(
+      MakeThereAndBackAgainData(42ul).SerializeAsString());
+  WaitForIdleTaskQueue();
+
+  // Reach conditions.
+  stream_->ReportSliceViewed(
+      surface.GetSurfaceId(),
+      surface.initial_state->updated_slices(1).slice().slice_id());
+
+  stream_->OnEnterBackground();
+  WaitForIdleTaskQueue();
+
+  // Verify that the ThereAndBackAgain action was uploaded.
+  ASSERT_EQ(1, network_.action_request_call_count);
+  EXPECT_EQ(1, network_.action_request_sent->feed_actions_size());
+}
+
+TEST_F(FeedStreamConditionalActionsUploadTest,
+       AllowActionsUploadWhenNoticeCardNotPresentRegardlessOfConditions) {
+  auto model_state = MakeTypicalInitialModelState();
+  model_state->stream_data.set_privacy_notice_fulfilled(false);
+  response_translator_.InjectResponse(std::move(model_state));
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  // Process the view action and the ThereAndBackAgain action while the upload
+  // conditions aren't reached.
+  stream_->ProcessViewAction(MakeFeedAction(42ul).SerializeAsString());
+  WaitForIdleTaskQueue();
+  stream_->ProcessThereAndBackAgain(
+      MakeThereAndBackAgainData(42ul).SerializeAsString());
+  WaitForIdleTaskQueue();
+
+  // Trigger an upload through a query.
+  stream_->OnEnterBackground();
+  WaitForIdleTaskQueue();
+
+  // Verify the ThereAndBackAgain action and the view action were uploaded.
+  ASSERT_EQ(1, network_.action_request_call_count);
+  EXPECT_EQ(2, network_.action_request_sent->feed_actions_size());
+}
+
+TEST_F(FeedStreamConditionalActionsUploadTest,
+       ReupdateUploadEnableBitsOnSignIn) {
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  // Reach conditions.
+  stream_->ReportSliceViewed(
+      surface.GetSurfaceId(),
+      surface.initial_state->updated_slices(1).slice().slice_id());
+
+  // Assert that uploads are not yet enabled.
+  ASSERT_FALSE(stream_->CanUploadActions());
+
+  // Update the upload enable bits which will enable upload because the related
+  // pref is true.
+  stream_->OnSignedIn();
+
+  EXPECT_TRUE(stream_->CanUploadActions());
+}
+
+TEST_F(FeedStreamConditionalActionsUploadTest,
+       ResetTheUploadEnableBitsOnSignOut) {
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  // Reach conditions.
+  stream_->ReportSliceViewed(
+      surface.GetSurfaceId(),
+      surface.initial_state->updated_slices(1).slice().slice_id());
+
+  // Update the upload enable bits which will enable upload.
+  stream_->OnSignedOut();
+
+  ASSERT_TRUE(stream_->CanUploadActions());
+}
+
 TEST_F(FeedStreamTest, LoadStreamFromNetworkUploadsActions) {
   stream_->UploadAction(MakeFeedAction(99ul), false, base::DoNothing());
   WaitForIdleTaskQueue();
@@ -1661,6 +1869,37 @@
   }
 }
 
+TEST_F(FeedStreamConditionalActionsUploadTest,
+       LoadMoreDoesntUpdateNoticeCardPref) {
+  // The initial stream load has the notice card.
+  response_translator_.InjectResponse(MakeTypicalInitialModelState());
+  TestSurface surface(stream_.get());
+  WaitForIdleTaskQueue();
+
+  // Inject a response for the LoadMore fetch that doesn't have the notice card.
+  // It shouldn't overwrite the notice card pref.
+  response_translator_.InjectResponse(MakeTypicalNextPageState(
+      /* first_cluster_id= */ 0,
+      /* last_added_time= */ kTestTimeEpoch,
+      /* signed_in= */ true,
+      /* logging_enabled= */ true,
+      /* privacy_notice_fulfilled= */ false));
+  stream_->LoadMore(surface.GetSurfaceId(), base::DoNothing());
+  WaitForIdleTaskQueue();
+
+  // Process a view action that should be dropped because the upload of actions
+  // is still disabled because there is still a notice card.
+  stream_->ProcessViewAction(MakeFeedAction(42ul).SerializeAsString());
+  WaitForIdleTaskQueue();
+
+  // Trigger an upload.
+  stream_->OnEnterBackground();
+  WaitForIdleTaskQueue();
+
+  // Verify that there were no uploads.
+  EXPECT_EQ(0, network_.action_request_call_count);
+}
+
 TEST_F(FeedStreamTest, BackgroundingAppUploadsActions) {
   stream_->UploadAction(MakeFeedAction(1ul), false, base::DoNothing());
   stream_->OnEnterBackground();
diff --git a/components/feed/core/v2/prefs.cc b/components/feed/core/v2/prefs.cc
index 84ee668..562ff7d 100644
--- a/components/feed/core/v2/prefs.cc
+++ b/components/feed/core/v2/prefs.cc
@@ -86,6 +86,26 @@
   pref_service.ClearPref(feed::prefs::kClientInstanceId);
 }
 
+void SetLastFetchHadNoticeCard(PrefService& pref_service, bool value) {
+  pref_service.SetBoolean(feed::prefs::kLastFetchHadNoticeCard, value);
+}
+
+bool GetLastFetchHadNoticeCard(const PrefService& pref_service) {
+  return pref_service.GetBoolean(feed::prefs::kLastFetchHadNoticeCard);
+}
+
+void SetHasReachedClickAndViewActionsUploadConditions(PrefService& pref_service,
+                                                      bool value) {
+  pref_service.SetBoolean(
+      feed::prefs::kHasReachedClickAndViewActionsUploadConditions, value);
+}
+
+bool GetHasReachedClickAndViewActionsUploadConditions(
+    const PrefService& pref_service) {
+  return pref_service.GetBoolean(
+      feed::prefs::kHasReachedClickAndViewActionsUploadConditions);
+}
+
 }  // namespace prefs
 
 }  // namespace feed
diff --git a/components/feed/core/v2/prefs.h b/components/feed/core/v2/prefs.h
index 4b11847..634bddb1 100644
--- a/components/feed/core/v2/prefs.h
+++ b/components/feed/core/v2/prefs.h
@@ -44,6 +44,14 @@
 std::string GetClientInstanceId(PrefService& pref_service);
 void ClearClientInstanceId(PrefService& pref_service);
 
+void SetLastFetchHadNoticeCard(PrefService& pref_service, bool value);
+bool GetLastFetchHadNoticeCard(const PrefService& pref_service);
+
+void SetHasReachedClickAndViewActionsUploadConditions(PrefService& pref_service,
+                                                      bool value);
+bool GetHasReachedClickAndViewActionsUploadConditions(
+    const PrefService& pref_service);
+
 }  // namespace prefs
 }  // namespace feed
 
diff --git a/components/feed/core/v2/tasks/load_stream_task.cc b/components/feed/core/v2/tasks/load_stream_task.cc
index a9da4fc..14d75666 100644
--- a/components/feed/core/v2/tasks/load_stream_task.cc
+++ b/components/feed/core/v2/tasks/load_stream_task.cc
@@ -167,6 +167,10 @@
           *response_data.model_update_request),
       base::DoNothing());
 
+  stream_->SetLastStreamLoadHadNoticeCard(
+      response_data.model_update_request->stream_data
+          .privacy_notice_fulfilled());
+
   if (load_type_ != LoadType::kBackgroundRefresh) {
     auto model = std::make_unique<StreamModel>();
     model->Update(std::move(response_data.model_update_request));
diff --git a/components/feed/core/v2/tasks/upload_actions_task.cc b/components/feed/core/v2/tasks/upload_actions_task.cc
index 0532c83d..4b51bdb 100644
--- a/components/feed/core/v2/tasks/upload_actions_task.cc
+++ b/components/feed/core/v2/tasks/upload_actions_task.cc
@@ -177,6 +177,11 @@
     return;
   }
 
+  if (!stream_->CanUploadActions()) {
+    Done(UploadActionsStatus::kAbortUploadBecauseDisabled);
+    return;
+  }
+
   // If the new action was stored and upload_now was set, load all pending
   // actions and try to upload.
   ReadActions();
@@ -204,6 +209,10 @@
     Done(UploadActionsStatus::kAbortUploadForSignedOutUser);
     return;
   }
+  if (!stream_->CanUploadActions()) {
+    Done(UploadActionsStatus::kAbortUploadBecauseDisabled);
+    return;
+  }
   UpdateAndUploadNextBatch();
 }
 
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index cab56346..8065e08 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -43,6 +43,9 @@
 const base::Feature kInterestFeedV1ClicksAndViewsConditionalUpload{
     "InterestFeedV1ClickAndViewActionsConditionalUpload",
     base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kInterestFeedV2ClicksAndViewsConditionalUpload{
+    "InterestFeedV2ClickAndViewActionsConditionalUpload",
+    base::FEATURE_DISABLED_BY_DEFAULT};
 
 const char kDefaultReferrerUrl[] =
     "https://www.googleapis.com/auth/chrome-content-suggestions";
diff --git a/components/feed/feed_feature_list.h b/components/feed/feed_feature_list.h
index 1b393c54..3fd0e3cb 100644
--- a/components/feed/feed_feature_list.h
+++ b/components/feed/feed_feature_list.h
@@ -31,6 +31,7 @@
 extern const base::Feature kReportFeedUserActions;
 
 extern const base::Feature kInterestFeedV1ClicksAndViewsConditionalUpload;
+extern const base::Feature kInterestFeedV2ClicksAndViewsConditionalUpload;
 
 std::string GetFeedReferrerUrl();
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index dff3a49..1feee329 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -29816,6 +29816,7 @@
              without receiving a new consistency token"/>
   <int value="6"
       label="Upload attempt was aborted because the account is now signed-out"/>
+  <int value="7" label="Could not upload because does not meet conditions"/>
 </enum>
 
 <enum name="FeedUserActionType">