Perfetto: Add game mode intervention data source

Bug: 219543620
Doc: go/game-dashboard-information-to-perfetto
Test: tools/run_android_test out/android perfetto_unittests
Change-Id: I5d2a10d5f6da384d9d9941f4a05f7dbf32fe3827
diff --git a/Android.bp b/Android.bp
index 4cb171f..448e0d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -534,6 +534,7 @@
         ":perfetto_src_protozero_filtering_bytecode_parser",
         ":perfetto_src_protozero_filtering_message_filter",
         ":perfetto_src_protozero_protozero",
+        ":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
         ":perfetto_src_traced_probes_android_log_android_log",
         ":perfetto_src_traced_probes_common_common",
         ":perfetto_src_traced_probes_data_source",
@@ -1097,6 +1098,7 @@
         ":perfetto_src_protozero_filtering_bytecode_parser",
         ":perfetto_src_protozero_filtering_message_filter",
         ":perfetto_src_protozero_protozero",
+        ":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
         ":perfetto_src_traced_probes_android_log_android_log",
         ":perfetto_src_traced_probes_common_common",
         ":perfetto_src_traced_probes_data_source",
@@ -1358,6 +1360,7 @@
         ":perfetto_src_protozero_filtering_bytecode_parser",
         ":perfetto_src_protozero_filtering_message_filter",
         ":perfetto_src_protozero_protozero",
+        ":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
         ":perfetto_src_traced_probes_android_log_android_log",
         ":perfetto_src_traced_probes_common_common",
         ":perfetto_src_traced_probes_data_source",
@@ -1839,6 +1842,7 @@
         ":perfetto_src_trace_processor_util_protozero_to_text",
         ":perfetto_src_trace_processor_util_util",
         ":perfetto_src_trace_processor_views_views",
+        ":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
         ":perfetto_src_traced_probes_android_log_android_log",
         ":perfetto_src_traced_probes_common_common",
         ":perfetto_src_traced_probes_data_source",
@@ -8996,6 +9000,22 @@
     ],
 }
 
+// GN: //src/traced/probes/android_game_intervention_list:android_game_intervention_list
+filegroup {
+    name: "perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
+    srcs: [
+        "src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.cc",
+    ],
+}
+
+// GN: //src/traced/probes/android_game_intervention_list:unittests
+filegroup {
+    name: "perfetto_src_traced_probes_android_game_intervention_list_unittests",
+    srcs: [
+        "src/traced/probes/android_game_intervention_list/android_game_intervention_list_unittest.cc",
+    ],
+}
+
 // GN: //src/traced/probes/android_log:android_log
 filegroup {
     name: "perfetto_src_traced_probes_android_log_android_log",
@@ -10148,6 +10168,8 @@
         ":perfetto_src_trace_processor_util_util",
         ":perfetto_src_trace_processor_views_unittests",
         ":perfetto_src_trace_processor_views_views",
+        ":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
+        ":perfetto_src_traced_probes_android_game_intervention_list_unittests",
         ":perfetto_src_traced_probes_android_log_android_log",
         ":perfetto_src_traced_probes_android_log_unittests",
         ":perfetto_src_traced_probes_common_common",
diff --git a/BUILD b/BUILD
index 608dd87..d293f71 100644
--- a/BUILD
+++ b/BUILD
@@ -227,6 +227,7 @@
         ":src_protozero_filtering_bytecode_common",
         ":src_protozero_filtering_bytecode_parser",
         ":src_protozero_filtering_message_filter",
+        ":src_traced_probes_android_game_intervention_list_android_game_intervention_list",
         ":src_traced_probes_android_log_android_log",
         ":src_traced_probes_common_common",
         ":src_traced_probes_data_source",
@@ -1722,6 +1723,15 @@
     ],
 )
 
+# GN target: //src/traced/probes/android_game_intervention_list:android_game_intervention_list
+perfetto_filegroup(
+    name = "src_traced_probes_android_game_intervention_list_android_game_intervention_list",
+    srcs = [
+        "src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.cc",
+        "src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.h",
+    ],
+)
+
 # GN target: //src/traced/probes/android_log:android_log
 perfetto_filegroup(
     name = "src_traced_probes_android_log_android_log",
diff --git a/src/traced/probes/BUILD.gn b/src/traced/probes/BUILD.gn
index 039f6e6..c60ba95 100644
--- a/src/traced/probes/BUILD.gn
+++ b/src/traced/probes/BUILD.gn
@@ -57,6 +57,7 @@
     "../../base",
     "../../tracing/core",
     "../../tracing/ipc/producer",
+    "android_game_intervention_list",
     "android_log",
     "common",
     "filesystem",
@@ -96,6 +97,7 @@
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
     "../../tracing/test:test_support",
+    "android_game_intervention_list:unittests",
     "android_log:unittests",
     "common:unittests",
     "filesystem:unittests",
diff --git a/src/traced/probes/android_game_intervention_list/BUILD.gn b/src/traced/probes/android_game_intervention_list/BUILD.gn
new file mode 100644
index 0000000..0827b66
--- /dev/null
+++ b/src/traced/probes/android_game_intervention_list/BUILD.gn
@@ -0,0 +1,48 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../gn/test.gni")
+
+source_set("android_game_intervention_list") {
+  public_deps = [ "../../../tracing/core" ]
+  deps = [
+    "..:data_source",
+    "../../../../gn:default_deps",
+    "../../../../include/perfetto/ext/traced",
+    "../../../../protos/perfetto/common:zero",
+    "../../../../protos/perfetto/config/android:zero",
+    "../../../../protos/perfetto/trace:zero",
+    "../../../../protos/perfetto/trace/android:zero",
+    "../../../base",
+  ]
+  sources = [
+    "android_game_intervention_list_data_source.cc",
+    "android_game_intervention_list_data_source.h",
+  ]
+}
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  deps = [
+    ":android_game_intervention_list",
+    "../../../../gn:default_deps",
+    "../../../../gn:gtest_and_gmock",
+    "../../../../protos/perfetto/config/android:cpp",
+    "../../../../protos/perfetto/trace/android:cpp",
+    "../../../../protos/perfetto/trace/android:zero",
+    "../../../../src/base:test_support",
+    "../../../../src/tracing/test:test_support",
+  ]
+  sources = [ "android_game_intervention_list_unittest.cc" ]
+}
diff --git a/src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.cc b/src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.cc
new file mode 100644
index 0000000..5d8f6c4
--- /dev/null
+++ b/src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.cc
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.h"
+
+#include <stddef.h>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/tracing/core/trace_writer.h"
+
+#include "perfetto/tracing/core/data_source_config.h"
+#include "protos/perfetto/config/android/android_game_intervention_list_config.pbzero.h"
+#include "protos/perfetto/trace/android/android_game_intervention_list.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+
+const char kAndroidGameInterventionListFileName[] =
+    "/data/system/game_mode_intervention.list";
+
+// making the descriptor static
+const ProbesDataSource::Descriptor
+    AndroidGameInterventionListDataSource::descriptor = {
+        /* name */ "android.game_interventions",
+        /* flags */ Descriptor::kFlagsNone,
+        /* fill_descriptor_func */ nullptr,
+};
+
+AndroidGameInterventionListDataSource::AndroidGameInterventionListDataSource(
+    const DataSourceConfig& ds_config,
+    TracingSessionID session_id,
+    std::unique_ptr<TraceWriter> trace_writer)
+    : ProbesDataSource(session_id, &descriptor),
+      trace_writer_(std::move(trace_writer)) {
+  perfetto::protos::pbzero::AndroidGameInterventionListConfig::Decoder cfg(
+      ds_config.android_game_intervention_list_config_raw());
+  for (auto name = cfg.package_name_filter(); name; ++name) {
+    package_name_filter_.emplace_back((*name).ToStdString());
+  }
+}
+
+AndroidGameInterventionListDataSource::
+    ~AndroidGameInterventionListDataSource() = default;
+
+void AndroidGameInterventionListDataSource::Start() {
+  auto trace_packet = trace_writer_->NewTracePacket();
+  auto* android_game_intervention_list_packet =
+      trace_packet->set_android_game_intervention_list();
+
+  base::ScopedFstream fs(fopen(kAndroidGameInterventionListFileName, "r"));
+  if (!fs) {
+    PERFETTO_ELOG("Failed to open %s", kAndroidGameInterventionListFileName);
+    android_game_intervention_list_packet->set_read_error(true);
+  } else {
+    bool is_parsed_fully = ParseAndroidGameInterventionListStream(
+        android_game_intervention_list_packet, fs, package_name_filter_);
+    if (!is_parsed_fully) {
+      android_game_intervention_list_packet->set_parse_error(true);
+    }
+    if (ferror(*fs)) {
+      android_game_intervention_list_packet->set_read_error(true);
+    }
+  }
+
+  trace_packet->Finalize();
+  trace_writer_->Flush();
+}
+
+void AndroidGameInterventionListDataSource::Flush(
+    FlushRequestID,
+    std::function<void()> callback) {
+  callback();
+}
+
+bool AndroidGameInterventionListDataSource::
+    ParseAndroidGameInterventionListStream(
+        protos::pbzero::AndroidGameInterventionList* packet,
+        const base::ScopedFstream& fs,
+        const std::vector<std::string>& package_name_filter) {
+  bool is_parsed_fully = true;
+  char line_buf[2048];
+  while (fgets(line_buf, sizeof(line_buf), *fs) != nullptr) {
+    // removing trailing '\n'
+    // for processing fields with CStringTo* functions
+    line_buf[strlen(line_buf) - 1] = '\0';
+
+    if (!ParseAndroidGameInterventionListLine(line_buf, package_name_filter,
+                                              packet)) {
+      // marking parsed with error and continue with this line skipped
+      is_parsed_fully = false;
+    }
+  }
+  return is_parsed_fully;
+}
+
+bool AndroidGameInterventionListDataSource::
+    ParseAndroidGameInterventionListLine(
+        char* line,
+        const std::vector<std::string>& package_name_filter,
+        protos::pbzero::AndroidGameInterventionList* packet) {
+  size_t idx = 0;
+  perfetto::protos::pbzero::AndroidGameInterventionList_GamePackageInfo*
+      package = nullptr;
+  perfetto::protos::pbzero::AndroidGameInterventionList_GameModeInfo*
+      game_mode_info = nullptr;
+  for (base::StringSplitter string_splitter(line, ' '); string_splitter.Next();
+       ++idx) {
+    // check if package name is in the name filter
+    // if not we skip parsing this line.
+    if (idx == 0) {
+      if (!package_name_filter.empty() &&
+          std::count(package_name_filter.begin(), package_name_filter.end(),
+                     string_splitter.cur_token()) == 0) {
+        return true;
+      }
+      package = packet->add_game_packages();
+    }
+
+    switch (idx) {
+      case 0: {
+        package->set_name(string_splitter.cur_token(),
+                          string_splitter.cur_token_size());
+        break;
+      }
+      case 1: {
+        base::Optional<uint64_t> uid =
+            base::CStringToUInt64(string_splitter.cur_token());
+        if (uid == base::nullopt) {
+          PERFETTO_DLOG("Failed to parse game_mode_intervention.list uid.");
+          return false;
+        }
+        package->set_uid(uid.value());
+        break;
+      }
+      case 2: {
+        base::Optional<uint32_t> cur_mode =
+            base::CStringToUInt32(string_splitter.cur_token());
+        if (cur_mode == base::nullopt) {
+          PERFETTO_DLOG(
+              "Failed to parse game_mode_intervention.list cur_mode.");
+          return false;
+        }
+        package->set_current_mode(cur_mode.value());
+        break;
+      }
+      case 3:
+      case 5: {
+        base::Optional<uint32_t> game_mode =
+            base::CStringToUInt32(string_splitter.cur_token());
+        if (game_mode == base::nullopt) {
+          PERFETTO_DLOG(
+              "Failed to parse game_mode_intervention.list game_mode.");
+          return false;
+        }
+        game_mode_info = package->add_game_mode_info();
+        game_mode_info->set_mode(game_mode.value());
+        break;
+      }
+      case 4:
+      case 6: {
+        for (base::StringSplitter intervention_splitter(
+                 string_splitter.cur_token(), ',');
+             intervention_splitter.Next();) {
+          base::StringSplitter value_splitter(intervention_splitter.cur_token(),
+                                              '=');
+          value_splitter.Next();
+          char* key = value_splitter.cur_token();
+          if (strcmp(key, "angle") == 0) {
+            value_splitter.Next();
+            base::Optional<uint32_t> use_angle =
+                base::CStringToUInt32(value_splitter.cur_token());
+            if (use_angle == base::nullopt) {
+              PERFETTO_DLOG(
+                  "Failed to parse game_mode_intervention.list use_angle.");
+              return false;
+            }
+            game_mode_info->set_use_angle(use_angle.value());
+          } else if (strcmp(key, "scaling") == 0) {
+            value_splitter.Next();
+            base::Optional<double> resolution_downscale =
+                base::CStringToDouble(value_splitter.cur_token());
+            if (resolution_downscale == base::nullopt) {
+              PERFETTO_DLOG(
+                  "Failed to parse game_mode_intervention.list "
+                  "resolution_downscale.");
+              return false;
+            }
+            game_mode_info->set_resolution_downscale(
+                static_cast<float>(resolution_downscale.value()));
+          } else if (strcmp(key, "fps") == 0) {
+            value_splitter.Next();
+            base::Optional<double> fps =
+                base::CStringToDouble(value_splitter.cur_token());
+            if (fps == base::nullopt) {
+              PERFETTO_DLOG("Failed to parse game_mode_intervention.list fps.");
+              return false;
+            }
+            game_mode_info->set_fps(static_cast<float>(fps.value()));
+          }
+        }
+        break;
+      }
+    }
+  }
+  return true;
+}
+
+}  // namespace perfetto
diff --git a/src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.h b/src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.h
new file mode 100644
index 0000000..94189eb
--- /dev/null
+++ b/src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACED_PROBES_ANDROID_GAME_INTERVENTION_LIST_ANDROID_GAME_INTERVENTION_LIST_DATA_SOURCE_H_
+#define SRC_TRACED_PROBES_ANDROID_GAME_INTERVENTION_LIST_ANDROID_GAME_INTERVENTION_LIST_DATA_SOURCE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/pipe.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
+
+#include "src/traced/probes/probes_data_source.h"
+
+namespace perfetto {
+
+// forward declaration of protos to be loaded
+namespace protos {
+namespace pbzero {
+class AndroidGameInterventionList;
+class AndroidGameInterventionList_GamePackageInfo;
+}  // namespace pbzero
+}  // namespace protos
+
+class TraceWriter;
+
+class AndroidGameInterventionListDataSource : public ProbesDataSource {
+ public:
+  static const ProbesDataSource::Descriptor descriptor;
+
+  AndroidGameInterventionListDataSource(
+      const DataSourceConfig& ds_config,
+      TracingSessionID session_id,
+      std::unique_ptr<TraceWriter> trace_writer);
+
+  ~AndroidGameInterventionListDataSource() override;
+
+  // ProbesDataSource implementaion.
+  void Start() override;
+  void Flush(FlushRequestID, std::function<void()> callback) override;
+
+  bool ParseAndroidGameInterventionListStream(
+      protos::pbzero::AndroidGameInterventionList*
+          android_game_intervention_list,
+      const base::ScopedFstream& fs,
+      const std::vector<std::string>& package_name_filter);
+
+ private:
+  bool ParseAndroidGameInterventionListLine(
+      char* line,
+      const std::vector<std::string>& package_name_filter,
+      protos::pbzero::AndroidGameInterventionList* packet);
+
+  std::vector<std::string> package_name_filter_;
+  std::unique_ptr<TraceWriter> trace_writer_;
+};
+
+}  // namespace perfetto
+
+#endif  // SRC_TRACED_PROBES_ANDROID_GAME_INTERVENTION_LIST_ANDROID_GAME_INTERVENTION_LIST_DATA_SOURCE_H_
diff --git a/src/traced/probes/android_game_intervention_list/android_game_intervention_list_unittest.cc b/src/traced/probes/android_game_intervention_list/android_game_intervention_list_unittest.cc
new file mode 100644
index 0000000..7c78384
--- /dev/null
+++ b/src/traced/probes/android_game_intervention_list/android_game_intervention_list_unittest.cc
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.h"
+
+#include <stdio.h>
+
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/pipe.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/tracing/core/data_source_config.h"
+
+#include "protos/perfetto/config/android/android_game_intervention_list_config.gen.h"
+#include "protos/perfetto/trace/android/android_game_intervention_list.gen.h"
+#include "protos/perfetto/trace/android/android_game_intervention_list.pbzero.h"
+
+#include "src/tracing/core/trace_writer_for_testing.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace {
+
+class TestAndroidGameInterventionDataSource
+    : public AndroidGameInterventionListDataSource {
+ public:
+  TestAndroidGameInterventionDataSource(
+      const DataSourceConfig& ds_config,
+      TracingSessionID session_id,
+      std::unique_ptr<TraceWriter> trace_writer)
+      : AndroidGameInterventionListDataSource(ds_config,
+                                              session_id,
+                                              std::move(trace_writer)) {}
+};
+
+class AndroidGameInterventionListDataSourceTest : public ::testing::Test {
+ protected:
+  AndroidGameInterventionListDataSourceTest() {}
+
+  void CreateInstance(const DataSourceConfig& config) {
+    auto writer =
+        std::unique_ptr<TraceWriterForTesting>(new TraceWriterForTesting());
+    trace_writer_ = writer.get();
+    data_source_.reset(new TestAndroidGameInterventionDataSource(
+        config, /* id */ 0, std::move(writer)));
+  }
+
+  std::unique_ptr<TestAndroidGameInterventionDataSource> data_source_;
+  TraceWriterForTesting* trace_writer_;
+};
+
+TEST_F(AndroidGameInterventionListDataSourceTest, NonEmptyNameFilter) {
+  static constexpr char kValidInterventionLines[] =
+      "com.test.one   1234 0 2 "
+      "angle=0,scaling=1.0,fps=60\n"
+      "com.test.two   1235 1 3 "
+      "angle=1,scaling=0.6,fps=45\n"
+      "com.test.three   1236 2 3 "
+      "angle=1,scaling=0.85,fps=30 "
+      "2 angle=0,scaling=0.75,fps=120\n";
+
+  CreateInstance(DataSourceConfig());
+
+  auto pipe = base::Pipe::Create();
+  PERFETTO_CHECK(write(pipe.wr.get(), kValidInterventionLines,
+                       sizeof(kValidInterventionLines) - 1) ==
+                 sizeof(kValidInterventionLines) - 1);
+  pipe.wr.reset();
+  auto file_stream = base::ScopedFstream(fdopen(pipe.rd.get(), "r"));
+  pipe.rd.release();
+
+  protozero::HeapBuffered<protos::pbzero::AndroidGameInterventionList>
+      android_game_intervention_list;
+  std::vector<std::string> name_filter = {"com.test.one", "com.test.three"};
+
+  ASSERT_TRUE(data_source_->ParseAndroidGameInterventionListStream(
+      android_game_intervention_list.get(), file_stream, name_filter));
+  protos::gen::AndroidGameInterventionList parsed;
+  parsed.ParseFromString(android_game_intervention_list.SerializeAsString());
+
+  EXPECT_FALSE(parsed.read_error());
+  EXPECT_FALSE(parsed.parse_error());
+
+  EXPECT_EQ(parsed.game_packages_size(), 2);
+  EXPECT_EQ(parsed.game_packages()[0].name(), "com.test.one");
+  EXPECT_EQ(parsed.game_packages()[0].uid(), 1234ul);
+  EXPECT_EQ(parsed.game_packages()[0].current_mode(), 0u);
+  EXPECT_EQ(parsed.game_packages()[0].game_mode_info_size(), 1);
+  EXPECT_EQ(parsed.game_packages()[0].game_mode_info()[0].mode(), 2u);
+  EXPECT_EQ(parsed.game_packages()[0].game_mode_info()[0].use_angle(), false);
+  EXPECT_EQ(
+      parsed.game_packages()[0].game_mode_info()[0].resolution_downscale(),
+      1.0f);
+  EXPECT_EQ(parsed.game_packages()[0].game_mode_info()[0].fps(), 60.0f);
+
+  EXPECT_EQ(parsed.game_packages()[1].name(), "com.test.three");
+  EXPECT_EQ(parsed.game_packages()[1].uid(), 1236ul);
+  EXPECT_EQ(parsed.game_packages()[1].current_mode(), 2u);
+  EXPECT_EQ(parsed.game_packages()[1].game_mode_info_size(), 2);
+  EXPECT_EQ(parsed.game_packages()[1].game_mode_info()[0].mode(), 3u);
+  EXPECT_EQ(parsed.game_packages()[1].game_mode_info()[0].use_angle(), true);
+  EXPECT_EQ(
+      parsed.game_packages()[1].game_mode_info()[0].resolution_downscale(),
+      0.85f);
+  EXPECT_EQ(parsed.game_packages()[1].game_mode_info()[0].fps(), 30.0f);
+  EXPECT_EQ(parsed.game_packages()[1].game_mode_info()[1].mode(), 2u);
+  EXPECT_EQ(parsed.game_packages()[1].game_mode_info()[1].use_angle(), false);
+  EXPECT_EQ(
+      parsed.game_packages()[1].game_mode_info()[1].resolution_downscale(),
+      0.75f);
+  EXPECT_EQ(parsed.game_packages()[1].game_mode_info()[1].fps(), 120.0f);
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/traced/probes/probes_producer.cc b/src/traced/probes/probes_producer.cc
index 8b9fc46..702168d 100644
--- a/src/traced/probes/probes_producer.cc
+++ b/src/traced/probes/probes_producer.cc
@@ -35,6 +35,7 @@
 #include "perfetto/tracing/core/forward_decls.h"
 #include "perfetto/tracing/core/trace_config.h"
 #include "src/android_stats/statsd_logging_helper.h"
+#include "src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.h"
 #include "src/traced/probes/android_log/android_log_data_source.h"
 #include "src/traced/probes/common/cpu_freq_info.h"
 #include "src/traced/probes/filesystem/inode_file_data_source.h"
@@ -221,6 +222,17 @@
 
 template <>
 std::unique_ptr<ProbesDataSource>
+ProbesProducer::CreateDSInstance<AndroidGameInterventionListDataSource>(
+    TracingSessionID session_id,
+    const DataSourceConfig& config) {
+  auto buffer_id = static_cast<BufferID>(config.target_buffer());
+  return std::unique_ptr<ProbesDataSource>(
+      new AndroidGameInterventionListDataSource(
+          config, session_id, endpoint_->CreateTraceWriter(buffer_id)));
+}
+
+template <>
+std::unique_ptr<ProbesDataSource>
 ProbesProducer::CreateDSInstance<SysStatsDataSource>(
     TracingSessionID session_id,
     const DataSourceConfig& config) {
@@ -281,11 +293,17 @@
 }
 
 const DataSourceTraits kAllDataSources[] = {
-    Ds<AndroidLogDataSource>(),   Ds<AndroidPowerDataSource>(),
-    Ds<FtraceDataSource>(),       Ds<InitialDisplayStateDataSource>(),
-    Ds<InodeFileDataSource>(),    Ds<LinuxPowerSysfsDataSource>(),
-    Ds<MetatraceDataSource>(),    Ds<PackagesListDataSource>(),
-    Ds<ProcessStatsDataSource>(), Ds<SysStatsDataSource>(),
+    Ds<AndroidLogDataSource>(),
+    Ds<AndroidPowerDataSource>(),
+    Ds<FtraceDataSource>(),
+    Ds<AndroidGameInterventionListDataSource>(),
+    Ds<InitialDisplayStateDataSource>(),
+    Ds<InodeFileDataSource>(),
+    Ds<LinuxPowerSysfsDataSource>(),
+    Ds<MetatraceDataSource>(),
+    Ds<PackagesListDataSource>(),
+    Ds<ProcessStatsDataSource>(),
+    Ds<SysStatsDataSource>(),
     Ds<SystemInfoDataSource>(),
 };