Send server experiment IDs in feed reliability log

Bug: 348442524
Change-Id: I0bf51f6dc8a652d142c3d55e0fd58dc75347d8da
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5663717
Reviewed-by: Jonathan Freed <[email protected]>
Reviewed-by: Adam Arcaro <[email protected]>
Code-Coverage: [email protected] <[email protected]>
Commit-Queue: Jian Li <[email protected]>
Reviewed-by: Tommy Nyquist <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1322338}
diff --git a/chrome/browser/feed/android/feed_reliability_logging_bridge.cc b/chrome/browser/feed/android/feed_reliability_logging_bridge.cc
index 7cb8a75..bb6907b9 100644
--- a/chrome/browser/feed/android/feed_reliability_logging_bridge.cc
+++ b/chrome/browser/feed/android/feed_reliability_logging_bridge.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/feed/android/feed_reliability_logging_bridge.h"
+
 #include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
 #include "base/time/time.h"
 #include "components/feed/core/v2/public/types.h"
 #include "net/base/net_errors.h"
@@ -13,6 +15,8 @@
 // Must come after all headers that specialize FromJniType() / ToJniType().
 #include "chrome/browser/feed/android/jni_headers/FeedReliabilityLoggingBridge_jni.h"
 
+using base::android::ScopedJavaLocalRef;
+
 namespace feed {
 namespace android {
 namespace {
@@ -248,6 +252,13 @@
       base::android::AttachCurrentThread(), java_ref_, success);
 }
 
+void FeedReliabilityLoggingBridge::ReportExperiments(
+    const std::vector<int32_t>& experiment_ids) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_FeedReliabilityLoggingBridge_reportExperiments(env, java_ref_,
+                                                      experiment_ids);
+}
+
 void FeedReliabilityLoggingBridge::Destroy(JNIEnv* env) {
   delete this;
 }
diff --git a/chrome/browser/feed/android/feed_reliability_logging_bridge.h b/chrome/browser/feed/android/feed_reliability_logging_bridge.h
index a5f9ee2..549e5870f 100644
--- a/chrome/browser/feed/android/feed_reliability_logging_bridge.h
+++ b/chrome/browser/feed/android/feed_reliability_logging_bridge.h
@@ -62,6 +62,7 @@
                                    int64_t server_send_timestamp_ns) override;
   void LogLoadMoreRequestFinished(int combined_network_status_code) override;
   void LogLoadMoreEnded(bool success) override;
+  void ReportExperiments(const std::vector<int32_t>& experiment_ids) override;
 
  private:
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedReliabilityLoggingBridge.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedReliabilityLoggingBridge.java
index d9aa472..1165e29 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedReliabilityLoggingBridge.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedReliabilityLoggingBridge.java
@@ -8,6 +8,7 @@
 
 import org.jni_zero.CalledByNative;
 import org.jni_zero.JNINamespace;
+import org.jni_zero.JniType;
 import org.jni_zero.NativeMethods;
 
 import org.chromium.chrome.browser.xsurface.feed.FeedLaunchReliabilityLogger;
@@ -198,6 +199,14 @@
         }
     }
 
+    @CalledByNative
+    public void reportExperiments(@JniType("std::vector<int32_t>") int[] experimentIds) {
+        mLaunchLogger.reportExperiments(experimentIds);
+        if (mUserInteractionLogger != null) {
+            mUserInteractionLogger.reportExperiments(experimentIds);
+        }
+    }
+
     public void onStreamUpdateFinished() {
         if (!mLaunchLogger.isLaunchInProgress()) return;
 
diff --git a/chrome/browser/feed/feed_service_factory.cc b/chrome/browser/feed/feed_service_factory.cc
index 29f2624..933f356 100644
--- a/chrome/browser/feed/feed_service_factory.cc
+++ b/chrome/browser/feed/feed_service_factory.cc
@@ -121,9 +121,9 @@
     // by design. We do not provide the variations IDs from the backend
     // and do not attach them to the X-Client-Data header.
     for (const auto& exp : experiments) {
-      for (const auto& group_name : exp.second) {
+      for (const auto& group : exp.second) {
         ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(exp.first,
-                                                                  group_name);
+                                                                  group.name);
       }
     }
   }
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 5107440..9debef6 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -250,6 +250,7 @@
 #include "components/feed/buildflags.h"
 #include "components/feed/core/common/pref_names.h"
 #include "components/feed/core/shared_prefs/pref_names.h"
+#include "components/feed/core/v2/ios_shared_prefs.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/accessibility/accessibility_prefs/android/accessibility_prefs_controller.h"
@@ -2808,6 +2809,11 @@
   syncer::SyncPrefs::MaybeMigrateAutofillToPerAccountPref(profile_prefs);
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+#if BUILDFLAG(IS_ANDROID)
+  // Added 06/2024
+  feed::prefs::MigrateObsoleteFeedExperimentPref_Jun_2024(profile_prefs);
+#endif  // BUILDFLAG(IS_ANDROID)
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 
diff --git a/components/BUILD.gn b/components/BUILD.gn
index c6399dc..2db2912 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -447,6 +447,7 @@
       "//components/enterprise/content:unit_tests",
       "//components/favicon/content:unit_tests",
       "//components/feed/core/v2:core_unit_tests",
+      "//components/feed/core/v2:ios_shared_unit_tests",
       "//components/file_access:unit_tests",
       "//components/gcm_driver/instance_id:unit_tests",
       "//components/heavy_ad_intervention:unit_tests",
diff --git a/components/feed/core/common/BUILD.gn b/components/feed/core/common/BUILD.gn
index a8ac913..2fb22043 100644
--- a/components/feed/core/common/BUILD.gn
+++ b/components/feed/core/common/BUILD.gn
@@ -18,16 +18,3 @@
     "//components/feed:feature_list",
   ]
 }
-
-source_set("feed_core_common_unit_tests") {
-  testonly = true
-  sources = [ "pref_names_unittest.cc" ]
-
-  # public_deps = [ ":test_helpers" ]
-  deps = [
-    ":feed_core_common",
-    "//base",
-    "//components/prefs:test_support",
-    "//testing/gtest",
-  ]
-}
diff --git a/components/feed/core/common/pref_names.cc b/components/feed/core/common/pref_names.cc
index 7693c77..d061153c 100644
--- a/components/feed/core/common/pref_names.cc
+++ b/components/feed/core/common/pref_names.cc
@@ -48,19 +48,19 @@
 const char kInfoCardStates[] = "feed.info_card_states";
 const char kHasSeenWebFeed[] = "webfeed.has_seen_feed";
 const char kLastBadgeAnimationTime[] = "webfeed.last_badge_animation_time";
-const char kExperimentsV2[] = "feedv2.experiments_v2";
+const char kExperimentsV3[] = "feedv2.experiments_v3";
 const char kInfoCardTrackingStateDict[] = "info-card-tracking-state-dict";
 
-// Deprecated October 2022
-const char kExperimentsDeprecated[] = "feedv2.experiments";
+// Deprecated June 2024
+const char kExperimentsV2Deprecated[] = "feedv2.experiments_v2";
 
 }  // namespace prefs
 
 // Deprecated prefs:
 namespace {
 
-void RegisterObsoletePrefsOct_2022(PrefRegistrySimple* registry) {
-  registry->RegisterDictionaryPref(prefs::kExperimentsDeprecated);
+void RegisterObsoletePrefsJun_2024(PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(prefs::kExperimentsV2Deprecated);
 }
 
 }  // namespace
@@ -95,7 +95,7 @@
   registry->RegisterBooleanPref(feed::prefs::kHasSeenWebFeed, false);
   registry->RegisterTimePref(feed::prefs::kLastBadgeAnimationTime,
                              base::Time());
-  registry->RegisterDictionaryPref(feed::prefs::kExperimentsV2);
+  registry->RegisterDictionaryPref(feed::prefs::kExperimentsV3);
   registry->RegisterDictionaryPref(feed::prefs::kInfoCardTrackingStateDict);
 
 #if BUILDFLAG(IS_IOS)
@@ -103,23 +103,7 @@
                                 false);
 #endif  // BUILDFLAG(IS_IOS)
 
-  RegisterObsoletePrefsOct_2022(registry);
-}
-
-void MigrateObsoleteProfilePrefsOct_2022(PrefService* prefs) {
-  const base::Value* val =
-      prefs->GetUserPrefValue(prefs::kExperimentsDeprecated);
-  const base::Value::Dict* old = val ? val->GetIfDict() : nullptr;
-  if (old) {
-    base::Value::Dict dict;
-    for (const auto kv : *old) {
-      base::Value::List list;
-      list.Append(kv.second.GetString());
-      dict.Set(kv.first, std::move(list));
-    }
-    prefs->SetDict(feed::prefs::kExperimentsV2, std::move(dict));
-  }
-  prefs->ClearPref(prefs::kExperimentsDeprecated);
+  RegisterObsoletePrefsJun_2024(registry);
 }
 
 }  // namespace feed
diff --git a/components/feed/core/common/pref_names.h b/components/feed/core/common/pref_names.h
index 5fcb126..2d298063 100644
--- a/components/feed/core/common/pref_names.h
+++ b/components/feed/core/common/pref_names.h
@@ -87,14 +87,14 @@
 // The pref name for when the user last saw badge animation for web feed.
 extern const char kLastBadgeAnimationTime[];
 // The pref name for storing the server experiments the client is in.
-extern const char kExperimentsV2[];
+extern const char kExperimentsV3[];
 // Contains a dictionary of tracking states for all info cards in the feed.
 extern const char kInfoCardTrackingStateDict[];
 
 // Deprecated prefs
 
 // The pref name for storing the server experiments the client is in.
-extern const char kExperimentsDeprecated[];
+extern const char kExperimentsV2Deprecated[];
 
 }  // namespace prefs
 
diff --git a/components/feed/core/common/pref_names_unittest.cc b/components/feed/core/common/pref_names_unittest.cc
deleted file mode 100644
index 2d99467..0000000
--- a/components/feed/core/common/pref_names_unittest.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/feed/core/common/pref_names.h"
-
-#include <string>
-
-#include "base/values.h"
-#include "components/prefs/testing_pref_service.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace feed {
-
-class PrefNamesTest : public testing::Test {
- protected:
-  PrefNamesTest() { feed::RegisterProfilePrefs(prefs_.registry()); }
-
-  TestingPrefServiceSimple prefs_;
-};
-
-TEST_F(PrefNamesTest, MigrateExperiments) {
-  base::Value::Dict dict;
-  dict.Set("Trial1", "Group1");
-  base::Value::Dict expected;
-  base::Value::List group_list;
-  group_list.Append("Group1");
-  expected.Set("Trial1", std::move(group_list));
-
-  // Set the old prefs dictionary.
-  prefs_.SetDict(prefs::kExperimentsDeprecated, std::move(dict));
-
-  // Migrate the prefs.
-  MigrateObsoleteProfilePrefsOct_2022(&prefs_);
-
-  ASSERT_TRUE(prefs_.HasPrefPath(prefs::kExperimentsV2));
-  EXPECT_EQ(expected, prefs_.GetDict(prefs::kExperimentsV2));
-}
-
-}  // namespace feed
diff --git a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
index 9f45c59..18d98906 100644
--- a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
+++ b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
@@ -2657,8 +2657,8 @@
 
 TEST_F(FeedApiTest, ExperimentsAreClearedOnClearAll) {
   Experiments e;
-  std::vector<std::string> group_list1{"Group1"};
-  std::vector<std::string> group_list2{"Group2"};
+  std::vector<ExperimentGroup> group_list1{{"Group1", 123}};
+  std::vector<ExperimentGroup> group_list2{{"Group2", 9999}};
   e["Trial1"] = group_list1;
   e["Trial2"] = group_list2;
   prefs::SetExperiments(e, profile_prefs_);
diff --git a/components/feed/core/v2/api_test/feed_api_test.cc b/components/feed/core/v2/api_test/feed_api_test.cc
index 35f0f7f0..4cf430c 100644
--- a/components/feed/core/v2/api_test/feed_api_test.cc
+++ b/components/feed/core/v2/api_test/feed_api_test.cc
@@ -511,6 +511,9 @@
       base::StrCat({"LogLoadMoreEnded success=", success ? "true" : "false"}));
 }
 
+void TestReliabilityLoggingBridge::ReportExperiments(
+    const std::vector<int32_t>& experiment_ids) {}
+
 TestImageFetcher::TestImageFetcher(
     scoped_refptr<::network::SharedURLLoaderFactory> url_loader_factory)
     : ImageFetcher(url_loader_factory) {}
diff --git a/components/feed/core/v2/api_test/feed_api_test.h b/components/feed/core/v2/api_test/feed_api_test.h
index 7a84ee2d..7376db5 100644
--- a/components/feed/core/v2/api_test/feed_api_test.h
+++ b/components/feed/core/v2/api_test/feed_api_test.h
@@ -122,6 +122,7 @@
                                    int64_t server_send_timestamp_ns) override;
   void LogLoadMoreRequestFinished(int canonical_status) override;
   void LogLoadMoreEnded(bool success) override;
+  void ReportExperiments(const std::vector<int32_t>& experiment_ids) override;
 
  private:
   std::vector<std::string> events_;
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index 39bf2d8e..00be21dc 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -475,6 +475,19 @@
 void FeedStream::UpdateExperiments(Experiments experiments) {
   delegate_->RegisterExperiments(experiments);
   prefs::SetExperiments(experiments, *profile_prefs_);
+
+  std::vector<int32_t> experiment_ids;
+  for (const auto& e : experiments) {
+    for (const auto& g : e.second) {
+      experiment_ids.push_back(g.experiment_id);
+    }
+  }
+  for (auto& item : streams_) {
+    if (!item.second.surfaces.empty()) {
+      item.second.surface_updater->launch_reliability_logger()
+          .ReportExperiments(experiment_ids);
+    }
+  }
 }
 
 SurfaceId FeedStream::CreateSurface(const StreamType& type,
diff --git a/components/feed/core/v2/ios_shared_experiments_translator.cc b/components/feed/core/v2/ios_shared_experiments_translator.cc
index b2b3289a..e933cff 100644
--- a/components/feed/core/v2/ios_shared_experiments_translator.cc
+++ b/components/feed/core/v2/ios_shared_experiments_translator.cc
@@ -8,10 +8,15 @@
 #include <string>
 #include <vector>
 
+#include "base/strings/string_number_conversions.h"
 #include "components/feed/feed_feature_list.h"
 
 namespace feed {
 
+bool ExperimentGroup::operator==(const ExperimentGroup& rhs) const {
+  return name == rhs.name && experiment_id == rhs.experiment_id;
+}
+
 std::optional<Experiments> TranslateExperiments(
     const google::protobuf::RepeatedPtrField<feedwire::Experiment>&
         wire_experiments) {
@@ -23,20 +28,16 @@
       if (exp.has_trial_name() && exp.has_group_name()) {
         // Extract experiment in response that contains both trial and
         // group names.
-        if (e.find(exp.trial_name()) != e.end()) {
-          e[exp.trial_name()].push_back(exp.group_name());
-        } else {
-          e[exp.trial_name()] = {exp.group_name()};
+        ExperimentGroup group;
+        group.name = exp.group_name();
+        group.experiment_id = 0;
+        if (exp.has_experiment_id()) {
+          base::StringToInt(exp.experiment_id(), &(group.experiment_id));
         }
-      } else if (exp.has_experiment_id() &&
-                 base::FeatureList::IsEnabled(kFeedExperimentIDTagging)) {
-        // Extract experiment in response that contains an experiment ID.
-        std::string trial_name =
-            exp.has_trial_name() ? exp.trial_name() : kDiscoverFeedExperiments;
-        if (e.find(trial_name) != e.end()) {
-          e[trial_name].push_back(exp.experiment_id());
+        if (e.find(exp.trial_name()) != e.end()) {
+          e[exp.trial_name()].push_back(group);
         } else {
-          e[trial_name] = {exp.experiment_id()};
+          e[exp.trial_name()] = {group};
         }
       }
     }
diff --git a/components/feed/core/v2/ios_shared_experiments_translator.h b/components/feed/core/v2/ios_shared_experiments_translator.h
index 1f462129..4f3f41d 100644
--- a/components/feed/core/v2/ios_shared_experiments_translator.h
+++ b/components/feed/core/v2/ios_shared_experiments_translator.h
@@ -14,11 +14,16 @@
 
 namespace feed {
 
-// A map of trial names (key) and list of group names/IDs (value)
-// sent from the server.
-typedef std::map<std::string, std::vector<std::string>> Experiments;
+struct ExperimentGroup {
+  std::string name;
+  int experiment_id;
 
-constexpr const char kDiscoverFeedExperiments[] = "DiscoverFeedExperiments";
+  bool operator==(const ExperimentGroup& rhs) const;
+};
+
+// A map of trial names (key) and list of groups.
+// sent from the server.
+typedef std::map<std::string, std::vector<ExperimentGroup>> Experiments;
 
 std::optional<Experiments> TranslateExperiments(
     const google::protobuf::RepeatedPtrField<feedwire::Experiment>&
diff --git a/components/feed/core/v2/ios_shared_experiments_translator_unittest.cc b/components/feed/core/v2/ios_shared_experiments_translator_unittest.cc
index 5c3202a..0fa70f6a 100644
--- a/components/feed/core/v2/ios_shared_experiments_translator_unittest.cc
+++ b/components/feed/core/v2/ios_shared_experiments_translator_unittest.cc
@@ -18,17 +18,13 @@
       delete;
   ~ExperimentsTranslatorTest() override = default;
 
-  void SetUp() override {
-    feature_list_.InitAndEnableFeature(kFeedExperimentIDTagging);
-  }
-
  protected:
   base::test::ScopedFeatureList feature_list_;
 };
 
-TEST_F(ExperimentsTranslatorTest, ExperimentsAreTranslated) {
+TEST_F(ExperimentsTranslatorTest, ExperimentsAreTranslatedForGroupNameOnly) {
   Experiments expected;
-  std::vector<std::string> group_list{"Group1"};
+  std::vector<ExperimentGroup> group_list{{"Group1"}};
   expected["Trial1"] = group_list;
 
   feedwire::ChromeFeedResponseMetadata metadata;
@@ -42,38 +38,19 @@
   EXPECT_EQ(e, expected);
 }
 
-TEST_F(ExperimentsTranslatorTest, ExperimentsAreTranslatedIDTaggingEnabled) {
+TEST_F(ExperimentsTranslatorTest,
+       ExperimentsAreTranslatedForBothGroupNameAndExperimentId) {
   Experiments expected;
-  std::vector<std::string> group_list1{"ID1"};
-  std::vector<std::string> group_list2{"ID2"};
-  expected[kDiscoverFeedExperiments] = group_list1;
-  expected["Trial1"] = group_list2;
-
-  feedwire::ChromeFeedResponseMetadata metadata;
-  auto* exp1 = metadata.add_experiments();
-  exp1->set_experiment_id("ID1");
-  auto* exp2 = metadata.add_experiments();
-  exp2->set_trial_name("Trial1");
-  exp2->set_experiment_id("ID2");
-
-  std::optional<Experiments> e = TranslateExperiments(metadata.experiments());
-  ASSERT_TRUE(e.has_value());
-
-  EXPECT_EQ(e, expected);
-}
-
-TEST_F(ExperimentsTranslatorTest, ExperimentsAreTranslatedIDTaggingDisabled) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(kFeedExperimentIDTagging);
-  Experiments expected;
-  std::vector<std::string> group_list{"Group1"};
+  std::vector<ExperimentGroup> group_list{{"Group1", 12345}};
   expected["Trial1"] = group_list;
 
   feedwire::ChromeFeedResponseMetadata metadata;
   auto* exp1 = metadata.add_experiments();
   exp1->set_trial_name("Trial1");
   exp1->set_group_name("Group1");
+  exp1->set_experiment_id("12345");
 
+  // Group name must be present.
   auto* exp2 = metadata.add_experiments();
   exp2->set_experiment_id("EXP_ID_NOT_TRANSLATED");
   auto* exp3 = metadata.add_experiments();
diff --git a/components/feed/core/v2/ios_shared_prefs.cc b/components/feed/core/v2/ios_shared_prefs.cc
index 63b2ebb..092d8de 100644
--- a/components/feed/core/v2/ios_shared_prefs.cc
+++ b/components/feed/core/v2/ios_shared_prefs.cc
@@ -10,6 +10,11 @@
 namespace feed {
 namespace prefs {
 
+namespace {
+const char kNameKey[] = "name";
+const char kIdKey[] = "id";
+}  // namespace
+
 void SetLastFetchHadNoticeCard(PrefService& pref_service, bool value) {
   pref_service.SetBoolean(feed::prefs::kLastFetchHadNoticeCard, value);
 }
@@ -53,25 +58,59 @@
   for (const auto& exp : experiments) {
     base::Value::List list;
     for (auto elem : exp.second) {
-      list.Append(elem);
+      base::Value::Dict group_dict;
+      group_dict.Set(kNameKey, elem.name);
+      group_dict.Set(kIdKey, elem.experiment_id);
+      list.Append(std::move(group_dict));
     }
     dict.Set(exp.first, std::move(list));
   }
-  pref_service.SetDict(kExperimentsV2, std::move(dict));
+  pref_service.SetDict(kExperimentsV3, std::move(dict));
 }
 
 Experiments GetExperiments(PrefService& pref_service) {
-  const auto& dict = pref_service.GetDict(kExperimentsV2);
+  const auto& dict = pref_service.GetDict(kExperimentsV3);
   Experiments experiments;
   for (auto kv : dict) {
-    std::vector<std::string> vect;
+    std::vector<ExperimentGroup> vect;
     for (const auto& v : kv.second.GetList()) {
-      vect.push_back(v.GetString());
+      ExperimentGroup group;
+      auto* group_dict = v.GetIfDict();
+      if (group_dict) {
+        const std::string* name = group_dict->FindString(kNameKey);
+        if (!name) {
+          continue;
+        }
+        group.name = *name;
+        group.experiment_id = group_dict->FindInt(kIdKey).value_or(0);
+      }
+      vect.push_back(group);
     }
     experiments[kv.first] = vect;
   }
   return experiments;
 }
 
+void MigrateObsoleteFeedExperimentPref_Jun_2024(PrefService* prefs) {
+  const base::Value* val =
+      prefs->GetUserPrefValue(prefs::kExperimentsV2Deprecated);
+  const base::Value::Dict* old = val ? val->GetIfDict() : nullptr;
+  if (old) {
+    Experiments experiments;
+    for (const auto kv : *old) {
+      std::vector<ExperimentGroup> vect;
+      for (const auto& v : kv.second.GetList()) {
+        ExperimentGroup group;
+        group.name = v.GetString();
+        group.experiment_id = 0;
+        vect.push_back(group);
+      }
+      experiments[kv.first] = vect;
+    }
+    SetExperiments(experiments, *prefs);
+  }
+  prefs->ClearPref(prefs::kExperimentsV2Deprecated);
+}
+
 }  // namespace prefs
 }  // namespace feed
diff --git a/components/feed/core/v2/ios_shared_prefs.h b/components/feed/core/v2/ios_shared_prefs.h
index b4823e79..98f3389 100644
--- a/components/feed/core/v2/ios_shared_prefs.h
+++ b/components/feed/core/v2/ios_shared_prefs.h
@@ -14,6 +14,7 @@
 using ::feed::Experiments;
 
 namespace prefs {
+
 void SetLastFetchHadNoticeCard(PrefService& pref_service, bool value);
 bool GetLastFetchHadNoticeCard(const PrefService& pref_service);
 
@@ -34,6 +35,9 @@
 // Set/get experiments into prefs.
 void SetExperiments(const Experiments& experiments, PrefService& pref_service);
 Experiments GetExperiments(PrefService& pref_service);
+
+void MigrateObsoleteFeedExperimentPref_Jun_2024(PrefService* prefs);
+
 }  // namespace prefs
 }  // namespace feed
 
diff --git a/components/feed/core/v2/ios_shared_prefs_unittest.cc b/components/feed/core/v2/ios_shared_prefs_unittest.cc
index d1e3afe..0011033 100644
--- a/components/feed/core/v2/ios_shared_prefs_unittest.cc
+++ b/components/feed/core/v2/ios_shared_prefs_unittest.cc
@@ -24,12 +24,40 @@
 
 TEST_F(IOSSharedPrefsTest, TestSetAndGetExperiments) {
   Experiments e;
-  std::vector<std::string> group_list{"Group1"};
-  e["Trial1"] = group_list;
+  std::vector<ExperimentGroup> group_list1{{"Group1", 123}, {"Group2", 9999}};
+  e["Trial1"] = group_list1;
+  std::vector<ExperimentGroup> group_list2{{"Hello", 12345}};
+  e["Trial2"] = group_list2;
 
   prefs::SetExperiments(e, prefs_);
 
-  ASSERT_TRUE(prefs_.HasPrefPath(prefs::kExperimentsV2));
+  ASSERT_TRUE(prefs_.HasPrefPath(prefs::kExperimentsV3));
+  EXPECT_EQ(e, prefs::GetExperiments(prefs_));
+}
+
+TEST_F(IOSSharedPrefsTest, MigrateExperimentsV2) {
+  // Save the experiments in the old format.
+  base::Value::Dict dict;
+  base::Value::List list1;
+  list1.Append("Group1");
+  list1.Append("Group2");
+  dict.Set("Trial1", std::move(list1));
+  base::Value::List list2;
+  list2.Append("Hello");
+  dict.Set("Trial2", std::move(list2));
+  prefs_.SetDict(prefs::kExperimentsV2Deprecated, std::move(dict));
+
+  prefs::MigrateObsoleteFeedExperimentPref_Jun_2024(&prefs_);
+
+  // Validate the migration.
+  ASSERT_FALSE(prefs_.HasPrefPath(prefs::kExperimentsV2Deprecated));
+  ASSERT_TRUE(prefs_.HasPrefPath(prefs::kExperimentsV3));
+
+  Experiments e;
+  std::vector<ExperimentGroup> group_list1{{"Group1", 0}, {"Group2", 0}};
+  e["Trial1"] = group_list1;
+  std::vector<ExperimentGroup> group_list2{{"Hello", 0}};
+  e["Trial2"] = group_list2;
   EXPECT_EQ(e, prefs::GetExperiments(prefs_));
 }
 
diff --git a/components/feed/core/v2/launch_reliability_logger.cc b/components/feed/core/v2/launch_reliability_logger.cc
index 263070d..e827afc 100644
--- a/components/feed/core/v2/launch_reliability_logger.cc
+++ b/components/feed/core/v2/launch_reliability_logger.cc
@@ -183,4 +183,12 @@
   }
 }
 
+void LaunchReliabilityLogger::ReportExperiments(
+    const std::vector<int32_t>& experiment_ids) {
+  for (const StreamSurfaceSet::Entry& entry : surfaces_->surfaces()) {
+    entry.renderer->GetReliabilityLoggingBridge().ReportExperiments(
+        experiment_ids);
+  }
+}
+
 }  // namespace feed
diff --git a/components/feed/core/v2/launch_reliability_logger.h b/components/feed/core/v2/launch_reliability_logger.h
index 4ab060c..16985bc 100644
--- a/components/feed/core/v2/launch_reliability_logger.h
+++ b/components/feed/core/v2/launch_reliability_logger.h
@@ -8,6 +8,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "components/feed/core/proto/v2/wire/reliability_logging_enums.pb.h"
+#include "components/feed/core/v2/ios_shared_experiments_translator.h"
 #include "components/feed/core/v2/public/reliability_logging_bridge.h"
 #include "components/feed/core/v2/public/surface_renderer.h"
 #include "components/feed/core/v2/public/types.h"
@@ -65,6 +66,8 @@
   void LogLaunchFinishedAfterStreamUpdate(
       feedwire::DiscoverLaunchResult result);
 
+  void ReportExperiments(const std::vector<int32_t>& experiment_ids);
+
  private:
   raw_ptr<StreamSurfaceSet, DanglingUntriaged> surfaces_;
   NetworkRequestId::Generator request_id_gen_;
diff --git a/components/feed/core/v2/protocol_translator_unittest.cc b/components/feed/core/v2/protocol_translator_unittest.cc
index c217997..55638ca 100644
--- a/components/feed/core/v2/protocol_translator_unittest.cc
+++ b/components/feed/core/v2/protocol_translator_unittest.cc
@@ -120,10 +120,6 @@
   ProtocolTranslatorTest& operator=(const ProtocolTranslatorTest&) = delete;
   ~ProtocolTranslatorTest() override = default;
 
-  void SetUp() override {
-    feature_list_.InitAndEnableFeature(kFeedExperimentIDTagging);
-  }
-
  protected:
   base::test::ScopedFeatureList feature_list_;
 };
@@ -225,7 +221,7 @@
 
 TEST_F(ProtocolTranslatorTest, ExperimentsAreTranslated) {
   Experiments expected;
-  std::vector<std::string> group_list{"Group1"};
+  std::vector<ExperimentGroup> group_list{{"Group1", 123}};
   expected["Trial1"] = group_list;
 
   feedwire::Response response = EmptyWireResponse();
@@ -235,65 +231,7 @@
                   ->add_experiments();
   exp->set_trial_name("Trial1");
   exp->set_group_name("Group1");
-
-  RefreshResponseData refresh = TranslateWireResponse(response);
-  ASSERT_TRUE(refresh.experiments.has_value());
-
-  EXPECT_EQ(refresh.experiments.value(), expected);
-}
-
-TEST_F(ProtocolTranslatorTest, ExperimentsAreTranslatedIDTaggingEnabled) {
-  Experiments expected;
-  std::vector<std::string> group_list1{"ID1"};
-  std::vector<std::string> group_list2{"ID2"};
-  expected[kDiscoverFeedExperiments] = group_list1;
-  expected["Trial1"] = group_list2;
-
-  feedwire::Response response = EmptyWireResponse();
-  auto* exp1 = response.mutable_feed_response()
-                   ->mutable_feed_response_metadata()
-                   ->mutable_chrome_feed_response_metadata()
-                   ->add_experiments();
-  exp1->set_experiment_id("ID1");
-  auto* exp2 = response.mutable_feed_response()
-                   ->mutable_feed_response_metadata()
-                   ->mutable_chrome_feed_response_metadata()
-                   ->add_experiments();
-  exp2->set_trial_name("Trial1");
-  exp2->set_experiment_id("ID2");
-
-  RefreshResponseData refresh = TranslateWireResponse(response);
-  ASSERT_TRUE(refresh.experiments.has_value());
-
-  EXPECT_EQ(refresh.experiments.value(), expected);
-}
-
-TEST_F(ProtocolTranslatorTest, ExperimentsAreTranslatedIDTaggingDisabled) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(kFeedExperimentIDTagging);
-  Experiments expected;
-  std::vector<std::string> group_list{"Group1"};
-  expected["Trial1"] = group_list;
-
-  feedwire::Response response = EmptyWireResponse();
-  auto* exp1 = response.mutable_feed_response()
-                   ->mutable_feed_response_metadata()
-                   ->mutable_chrome_feed_response_metadata()
-                   ->add_experiments();
-  exp1->set_trial_name("Trial1");
-  exp1->set_group_name("Group1");
-
-  auto* exp2 = response.mutable_feed_response()
-                   ->mutable_feed_response_metadata()
-                   ->mutable_chrome_feed_response_metadata()
-                   ->add_experiments();
-  exp2->set_experiment_id("EXP_ID_NOT_TRANSLATED");
-  auto* exp3 = response.mutable_feed_response()
-                   ->mutable_feed_response_metadata()
-                   ->mutable_chrome_feed_response_metadata()
-                   ->add_experiments();
-  exp3->set_trial_name("Trial_NOT_TRANSLATED");
-  exp3->set_experiment_id("EXP_ID_NOT_TRANSLATED");
+  exp->set_experiment_id("123");
 
   RefreshResponseData refresh = TranslateWireResponse(response);
   ASSERT_TRUE(refresh.experiments.has_value());
diff --git a/components/feed/core/v2/public/reliability_logging_bridge.h b/components/feed/core/v2/public/reliability_logging_bridge.h
index 2bed5d6..1e2d3a0 100644
--- a/components/feed/core/v2/public/reliability_logging_bridge.h
+++ b/components/feed/core/v2/public/reliability_logging_bridge.h
@@ -7,6 +7,7 @@
 
 #include "base/time/time.h"
 #include "components/feed/core/proto/v2/wire/reliability_logging_enums.pb.h"
+#include "components/feed/core/v2/ios_shared_experiments_translator.h"
 #include "components/feed/core/v2/public/stream_type.h"
 #include "components/feed/core/v2/public/types.h"
 
@@ -64,6 +65,9 @@
   virtual void LogLoadMoreRequestFinished(int canonical_status) = 0;
   virtual void LogLoadMoreEnded(bool success) = 0;
 
+  virtual void ReportExperiments(
+      const std::vector<int32_t>& experiment_ids) = 0;
+
   virtual ~ReliabilityLoggingBridge() = default;
 };
 
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index 6adb25b..9fe10f0 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -106,10 +106,6 @@
              "FeedNoViewCache",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kFeedExperimentIDTagging,
-             "FeedExperimentIDTagging",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kFeedShowSignInCommand,
              "FeedShowSignInCommand",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/feed/feed_feature_list.h b/components/feed/feed_feature_list.h
index 92297f1..c1e5dc35 100644
--- a/components/feed/feed_feature_list.h
+++ b/components/feed/feed_feature_list.h
@@ -88,9 +88,6 @@
 // When enabled, no view cache is used.
 BASE_DECLARE_FEATURE(kFeedNoViewCache);
 
-// When enabled, allow tagging experiments with only an experiment ID.
-BASE_DECLARE_FEATURE(kFeedExperimentIDTagging);
-
 // When enabled, allow show sign in command to request a user signs in / syncs.
 BASE_DECLARE_FEATURE(kFeedShowSignInCommand);
 
diff --git a/ios/chrome/browser/discover_feed/model/discover_feed_experiments_tracker.mm b/ios/chrome/browser/discover_feed/model/discover_feed_experiments_tracker.mm
index f8215004..c5bf0fd 100644
--- a/ios/chrome/browser/discover_feed/model/discover_feed_experiments_tracker.mm
+++ b/ios/chrome/browser/discover_feed/model/discover_feed_experiments_tracker.mm
@@ -27,9 +27,9 @@
   // by design. We do not provide the variations IDs from the backend
   // and do not attach them to the X-Client-Data header.
   for (const auto& exp : experiments) {
-    for (const auto& group_name : exp.second) {
+    for (const auto& group : exp.second) {
       IOSChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(exp.first,
-                                                                   group_name);
+                                                                   group.name);
     }
   }
 }