tp: add viewcapture parsing

Bug: 323166383
Change-Id: Ifa70b3008463ba4542c5716b953644bce988fe3c
diff --git a/Android.bp b/Android.bp
index c27bfd8..c750262 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12502,6 +12502,7 @@
         "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc",
+        "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc",
         "src/trace_processor/importers/proto/winscope/winscope_module.cc",
     ],
 }
@@ -13094,6 +13095,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/suspend.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/thread.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/winscope/inputmethod.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql",
         "src/trace_processor/perfetto_sql/stdlib/chrome/**/*.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/args.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/counters.sql",
diff --git a/BUILD b/BUILD
index 5d12e4c..78c6af4 100644
--- a/BUILD
+++ b/BUILD
@@ -1777,6 +1777,8 @@
         "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h",
+        "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc",
+        "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h",
         "src/trace_processor/importers/proto/winscope/winscope_module.cc",
         "src/trace_processor/importers/proto/winscope/winscope_module.h",
     ],
@@ -2509,6 +2511,7 @@
     name = "src_trace_processor_perfetto_sql_stdlib_android_winscope_winscope",
     srcs = [
         "src/trace_processor/perfetto_sql/stdlib/android/winscope/inputmethod.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql",
     ],
 )
 
diff --git a/protos/perfetto/trace/android/winscope.proto b/protos/perfetto/trace/android/winscope.proto
index d7927d8..50723ee 100644
--- a/protos/perfetto/trace/android/winscope.proto
+++ b/protos/perfetto/trace/android/winscope.proto
@@ -22,6 +22,7 @@
 import "protos/perfetto/trace/android/shell_transition.proto";
 import "protos/perfetto/trace/android/surfaceflinger_layers.proto";
 import "protos/perfetto/trace/android/surfaceflinger_transactions.proto";
+import "protos/perfetto/trace/android/viewcapture.proto";
 import "protos/perfetto/trace/android/winscope_extensions_impl.proto";
 
 // This file is used to generated descriptors for all the winscope protos.
@@ -32,4 +33,5 @@
   optional ShellTransition shell_transition = 3;
   optional ProtoLogMessage protolog_message = 4;
   optional WinscopeExtensionsImpl winscope_extensions = 5;
+  optional ViewCapture viewcapture = 6;
 }
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index 964a6ad..8b8d35c 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -154,6 +154,10 @@
         context_->storage->mutable_surfaceflinger_transactions_table(), id);
   }
 
+  BoundInserter AddArgsTo(tables::ViewCaptureTable::Id id) {
+    return AddArgsTo(context_->storage->mutable_viewcapture_table(), id);
+  }
+
   BoundInserter AddArgsTo(tables::WindowManagerShellTransitionsTable::Id id) {
     return AddArgsTo(
         context_->storage->mutable_window_manager_shell_transitions_table(),
diff --git a/src/trace_processor/importers/proto/winscope/BUILD.gn b/src/trace_processor/importers/proto/winscope/BUILD.gn
index 593b749..d22dee0 100644
--- a/src/trace_processor/importers/proto/winscope/BUILD.gn
+++ b/src/trace_processor/importers/proto/winscope/BUILD.gn
@@ -16,6 +16,8 @@
 
 source_set("full") {
   sources = [
+    "viewcapture_args_parser.cc",
+    "viewcapture_args_parser.h",
     "protolog_messages_tracker.cc",
     "protolog_messages_tracker.h",
     "protolog_parser.cc",
diff --git a/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc b/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc
new file mode 100644
index 0000000..ed88163
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 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/trace_processor/importers/proto/winscope/viewcapture_args_parser.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
+#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+ViewCaptureArgsParser::ViewCaptureArgsParser(
+    int64_t packet_timestamp,
+    ArgsTracker::BoundInserter& inserter,
+    TraceStorage& storage,
+    PacketSequenceStateGeneration* sequence_state)
+    : ArgsParser(packet_timestamp, inserter, storage, sequence_state),
+      storage_{storage} {}
+
+void ViewCaptureArgsParser::AddInteger(const Key& key, int64_t value) {
+  if (TryAddDeinternedString(key, static_cast<uint64_t>(value))) {
+    return;
+  }
+  ArgsParser::AddInteger(key, value);
+}
+
+void ViewCaptureArgsParser::AddUnsignedInteger(const Key& key, uint64_t value) {
+  if (TryAddDeinternedString(key, value)) {
+    return;
+  }
+  ArgsParser::AddUnsignedInteger(key, value);
+}
+
+bool ViewCaptureArgsParser::TryAddDeinternedString(const Key& key,
+                                                   uint64_t iid) {
+  bool is_interned_field = base::EndsWith(key.key, "_iid");
+  if (!is_interned_field) {
+    return false;
+  }
+
+  const auto deintern_key = key.key.substr(0, key.key.size() - 4);
+  const auto deintern_flat_key =
+      key.flat_key.substr(0, key.flat_key.size() - 4);
+  const auto deintern_key_combined = Key{deintern_flat_key, deintern_key};
+  const auto deintern_val = TryDeinternString(key, iid);
+
+  if (!deintern_val) {
+    ArgsParser::AddString(
+        deintern_key_combined,
+        protozero::ConstChars{ERROR_MSG.data(), ERROR_MSG.size()});
+    storage_.IncrementStats(
+        stats::winscope_viewcapture_missing_interned_string_parse_errors);
+    return false;
+  }
+
+  ArgsParser::AddString(deintern_key_combined, *deintern_val);
+  return true;
+}
+
+std::optional<protozero::ConstChars> ViewCaptureArgsParser::TryDeinternString(
+    const Key& key,
+    uint64_t iid) {
+  if (base::EndsWith(key.key, "class_name_iid")) {
+    auto* decoder =
+        seq_state()
+            ->LookupInternedMessage<
+                protos::pbzero::InternedData::kViewcaptureClassNameFieldNumber,
+                protos::pbzero::InternedString>(iid);
+    if (decoder) {
+      return protozero::ConstChars{
+          reinterpret_cast<const char*>(decoder->str().data),
+          decoder->str().size};
+    }
+  } else if (base::EndsWith(key.key, "package_name_iid")) {
+    auto* decoder =
+        seq_state()
+            ->LookupInternedMessage<protos::pbzero::InternedData::
+                                        kViewcapturePackageNameFieldNumber,
+                                    protos::pbzero::InternedString>(iid);
+    if (decoder) {
+      return protozero::ConstChars{
+          reinterpret_cast<const char*>(decoder->str().data),
+          decoder->str().size};
+    }
+  } else if (base::EndsWith(key.key, "view_id_iid")) {
+    auto* decoder =
+        seq_state()
+            ->LookupInternedMessage<
+                protos::pbzero::InternedData::kViewcaptureViewIdFieldNumber,
+                protos::pbzero::InternedString>(iid);
+    if (decoder) {
+      return protozero::ConstChars{
+          reinterpret_cast<const char*>(decoder->str().data),
+          decoder->str().size};
+    }
+  } else if (base::EndsWith(key.key, "window_name_iid")) {
+    auto* decoder =
+        seq_state()
+            ->LookupInternedMessage<
+                protos::pbzero::InternedData::kViewcaptureWindowNameFieldNumber,
+                protos::pbzero::InternedString>(iid);
+    if (decoder) {
+      return protozero::ConstChars{
+          reinterpret_cast<const char*>(decoder->str().data),
+          decoder->str().size};
+    }
+  }
+
+  return std::nullopt;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h b/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h
new file mode 100644
index 0000000..32b76b6
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_VIEWCAPTURE_ARGS_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_VIEWCAPTURE_ARGS_PARSER_H_
+
+#include <optional>
+
+#include "src/trace_processor/importers/proto/args_parser.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Specialized args parser to de-intern ViewCapture strings
+class ViewCaptureArgsParser : public ArgsParser {
+ public:
+  using Key = ArgsParser::Key;
+
+  ViewCaptureArgsParser(int64_t packet_timestamp,
+                        ArgsTracker::BoundInserter& inserter,
+                        TraceStorage& storage,
+                        PacketSequenceStateGeneration* sequence_state);
+  void AddInteger(const Key&, int64_t) override;
+  void AddUnsignedInteger(const Key&, uint64_t) override;
+
+ private:
+  bool TryAddDeinternedString(const Key&, uint64_t);
+  std::optional<protozero::ConstChars> TryDeinternString(const Key&, uint64_t);
+
+  const base::StringView ERROR_MSG{"STRING DE-INTERNING ERROR"};
+  TraceStorage& storage_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_VIEWCAPTURE_ARGS_PARSER_H_
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.cc b/src/trace_processor/importers/proto/winscope/winscope_module.cc
index 4bf2723..81c7a59 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.cc
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.cc
@@ -18,6 +18,7 @@
 #include "protos/perfetto/trace/android/winscope_extensions.pbzero.h"
 #include "protos/perfetto/trace/android/winscope_extensions_impl.pbzero.h"
 #include "src/trace_processor/importers/proto/args_parser.h"
+#include "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h"
 #include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
 
 namespace perfetto {
@@ -76,13 +77,15 @@
           decoder.protolog_viewer_config());
       return;
     case TracePacket::kWinscopeExtensionsFieldNumber:
-      ParseWinscopeExtensionsData(decoder.winscope_extensions(), timestamp);
+      ParseWinscopeExtensionsData(decoder.winscope_extensions(), timestamp,
+                                  data);
       return;
   }
 }
 
 void WinscopeModule::ParseWinscopeExtensionsData(protozero::ConstBytes blob,
-                                                 int64_t timestamp) {
+                                                 int64_t timestamp,
+                                                 const TracePacketData& data) {
   WinscopeExtensionsImpl::Decoder decoder(blob.data, blob.size);
 
   if (auto field =
@@ -97,6 +100,11 @@
                  WinscopeExtensionsImpl::kInputmethodServiceFieldNumber);
              field.valid()) {
     ParseInputMethodServiceData(timestamp, field.as_bytes());
+  } else if (field =
+                 decoder.Get(WinscopeExtensionsImpl::kViewcaptureFieldNumber);
+             field.valid()) {
+    ParseViewCaptureData(timestamp, field.as_bytes(),
+                         data.sequence_state.get());
   }
 }
 
@@ -159,5 +167,24 @@
   }
 }
 
+void WinscopeModule::ParseViewCaptureData(
+    int64_t timestamp,
+    protozero::ConstBytes blob,
+    PacketSequenceStateGeneration* sequence_state) {
+  tables::ViewCaptureTable::Row row;
+  row.ts = timestamp;
+  auto rowId = context_->storage->mutable_viewcapture_table()->Insert(row).id;
+
+  ArgsTracker tracker(context_);
+  auto inserter = tracker.AddArgsTo(rowId);
+  ViewCaptureArgsParser writer(timestamp, inserter, *context_->storage.get(),
+                               sequence_state);
+  base::Status status = args_parser_.ParseMessage(
+      blob, kViewCaptureProtoName, nullptr /* parse all fields */, writer);
+  if (!status.ok()) {
+    context_->storage->IncrementStats(stats::winscope_viewcapture_parse_errors);
+  }
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.h b/src/trace_processor/importers/proto/winscope/winscope_module.h
index c77fcb4..7f6ae08 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.h
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.h
@@ -42,21 +42,25 @@
 
  private:
   void ParseWinscopeExtensionsData(protozero::ConstBytes blob,
-                                   int64_t timestamp);
+                                   int64_t timestamp,
+                                   const TracePacketData&);
   void ParseInputMethodClientsData(int64_t timestamp,
                                    protozero::ConstBytes blob);
   void ParseInputMethodManagerServiceData(int64_t timestamp,
                                           protozero::ConstBytes blob);
   void ParseInputMethodServiceData(int64_t timestamp,
                                    protozero::ConstBytes blob);
+  void ParseViewCaptureData(int64_t timestamp,
+                            protozero::ConstBytes blob,
+                            PacketSequenceStateGeneration* sequence_state);
 
   static constexpr auto* kInputMethodClientsProtoName =
       ".perfetto.protos.InputMethodClientsTraceProto";
   static constexpr auto* kInputMethodManagerServiceProtoName =
       ".perfetto.protos.InputMethodManagerServiceTraceProto";
-
   static constexpr auto* kInputMethodServiceProtoName =
       ".perfetto.protos.InputMethodServiceTraceProto";
+  static constexpr auto* kViewCaptureProtoName = ".perfetto.protos.ViewCapture";
 
   TraceProcessorContext* const context_;
   DescriptorPool pool_;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/winscope/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/winscope/BUILD.gn
index 93abe35..ff7e3ef 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/winscope/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/winscope/BUILD.gn
@@ -17,5 +17,6 @@
 perfetto_sql_source_set("winscope") {
   sources = [
     "inputmethod.sql",
+    "viewcapture.sql",
   ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql b/src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql
new file mode 100644
index 0000000..77e68b5
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql
@@ -0,0 +1,29 @@
+--
+-- Copyright 2024 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
+--
+--     https://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.
+
+-- Android viewcapture (from android.viewcapture data source).
+CREATE PERFETTO VIEW android_viewcapture(
+  -- Snapshot id
+  id INT,
+  -- Timestamp when the snapshot was triggered
+  ts INT,
+  -- Extra args parsed from the proto message
+  arg_set_id INT
+) AS
+SELECT
+  id,
+  ts,
+  arg_set_id
+FROM __intrinsic_viewcapture;
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index c8d58ca..6238455 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -349,6 +349,14 @@
   F(winscope_protolog_missing_interned_stacktrace_parse_errors,                \
                                           kSingle,  kInfo,     kAnalysis,      \
       "Failed to find interned ProtoLog stacktrace."),                         \
+  F(winscope_viewcapture_parse_errors,                                         \
+                                          kSingle,  kInfo,     kAnalysis,      \
+      "ViewCapture packet has unknown fields, which results in some "          \
+      "arguments missing. You may need a newer version of trace processor "    \
+      "to parse them."),                                                       \
+  F(winscope_viewcapture_missing_interned_string_parse_errors,                 \
+                                          kSingle,  kInfo,     kAnalysis,      \
+      "Failed to find interned ViewCapture string."),                          \
   F(jit_unknown_frame,                    kSingle,  kDataLoss, kTrace,         \
       "Indicates that we were unable to determine the function for a frame in "\
       "a jitted memory region"),                                               \
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index ff280e1..efc667e 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -845,6 +845,13 @@
     return &surfaceflinger_transactions_table_;
   }
 
+  const tables::ViewCaptureTable& viewcapture_table() const {
+    return viewcapture_table_;
+  }
+  tables::ViewCaptureTable* mutable_viewcapture_table() {
+    return &viewcapture_table_;
+  }
+
   const tables::WindowManagerShellTransitionsTable&
   window_manager_shell_transitions_table() const {
     return window_manager_shell_transitions_table_;
@@ -1147,6 +1154,7 @@
   tables::SurfaceFlingerLayerTable surfaceflinger_layer_table_{&string_pool_};
   tables::SurfaceFlingerTransactionsTable surfaceflinger_transactions_table_{
       &string_pool_};
+  tables::ViewCaptureTable viewcapture_table_{&string_pool_};
   tables::WindowManagerShellTransitionsTable
       window_manager_shell_transitions_table_{&string_pool_};
   tables::WindowManagerShellTransitionHandlersTable
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 6093c83..2902edc 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -139,15 +139,16 @@
 InputMethodClientsTable::~InputMethodClientsTable() = default;
 InputMethodManagerServiceTable::~InputMethodManagerServiceTable() = default;
 InputMethodServiceTable::~InputMethodServiceTable() = default;
+ProtoLogTable::~ProtoLogTable() = default;
 SurfaceFlingerLayersSnapshotTable::~SurfaceFlingerLayersSnapshotTable() =
     default;
 SurfaceFlingerLayerTable::~SurfaceFlingerLayerTable() = default;
 SurfaceFlingerTransactionsTable::~SurfaceFlingerTransactionsTable() = default;
+ViewCaptureTable::~ViewCaptureTable() = default;
 WindowManagerShellTransitionsTable::~WindowManagerShellTransitionsTable() =
     default;
 WindowManagerShellTransitionHandlersTable::
     ~WindowManagerShellTransitionHandlersTable() = default;
-ProtoLogTable::~ProtoLogTable() = default;
 
 }  // namespace tables
 
diff --git a/src/trace_processor/tables/winscope_tables.py b/src/trace_processor/tables/winscope_tables.py
index 8c058aa..15b93bb 100644
--- a/src/trace_processor/tables/winscope_tables.py
+++ b/src/trace_processor/tables/winscope_tables.py
@@ -117,6 +117,22 @@
             'arg_set_id': 'Extra args parsed from the proto message',
         }))
 
+VIEWCAPTURE_TABLE = Table(
+    python_module=__file__,
+    class_name='ViewCaptureTable',
+    sql_name='__intrinsic_viewcapture',
+    columns=[
+        C('ts', CppInt64()),
+        C('arg_set_id', CppUint32()),
+    ],
+    tabledoc=TableDoc(
+        doc='ViewCapture',
+        group='Winscope',
+        columns={
+            'ts': 'The timestamp the views were captured',
+            'arg_set_id': 'Extra args parsed from the proto message',
+        }))
+
 WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE = Table(
     python_module=__file__,
     class_name='WindowManagerShellTransitionsTable',
@@ -182,6 +198,7 @@
     SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE,
     SURFACE_FLINGER_LAYER_TABLE,
     SURFACE_FLINGER_TRANSACTIONS_TABLE,
+    VIEWCAPTURE_TABLE,
     WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE,
     WINDOW_MANAGER_SHELL_TRANSITION_HANDLERS_TABLE,
 ]
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 117a737..fe25faf 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -891,6 +891,8 @@
   RegisterStaticTable(storage->surfaceflinger_layer_table());
   RegisterStaticTable(storage->surfaceflinger_transactions_table());
 
+  RegisterStaticTable(storage->viewcapture_table());
+
   RegisterStaticTable(storage->window_manager_shell_transitions_table());
   RegisterStaticTable(
       storage->window_manager_shell_transition_handlers_table());
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index e70dfa3..2434d26 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -57,6 +57,7 @@
 from diff_tests.parser.android.tests_shell_transitions import ShellTransitions
 from diff_tests.parser.android.tests_surfaceflinger_layers import SurfaceFlingerLayers
 from diff_tests.parser.android.tests_surfaceflinger_transactions import SurfaceFlingerTransactions
+from diff_tests.parser.android.tests_viewcapture import ViewCapture
 from diff_tests.parser.atrace.tests import Atrace
 from diff_tests.parser.atrace.tests_error_handling import AtraceErrorHandling
 from diff_tests.parser.chrome.tests import ChromeParser
@@ -206,6 +207,7 @@
       *ShellTransitions(index_path, 'parser/android',
                         'ShellTransitions').fetch(),
       *ProtoLog(index_path, 'parser/android', 'ProtoLog').fetch(),
+      *ViewCapture(index_path, 'parser/android', 'ViewCapture').fetch(),
       *TrackEvent(index_path, 'parser/track_event', 'TrackEvent').fetch(),
       *TranslatedArgs(index_path, 'parser/translated_args',
                       'TranslatedArgs').fetch(),
diff --git a/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py b/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py
new file mode 100644
index 0000000..e089ecb
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Path
+from python.generators.diff_tests.testing import Csv
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class ViewCapture(TestSuite):
+
+  def test_has_expected_rows(self):
+    return DiffTestBlueprint(
+        trace=Path('viewcapture.textproto'),
+        query="""
+        INCLUDE PERFETTO MODULE android.winscope.viewcapture;
+        SELECT
+          id, ts
+        FROM
+          android_viewcapture;
+        """,
+        out=Csv("""
+        "id","ts"
+        0,448881087865
+        1,448883575576
+        """))
+
+  def test_has_expected_args(self):
+    return DiffTestBlueprint(
+        trace=Path('viewcapture.textproto'),
+        query="""
+        INCLUDE PERFETTO MODULE android.winscope.viewcapture;
+        SELECT
+          args.key, args.display_value
+        FROM
+          android_viewcapture AS vc JOIN args ON vc.arg_set_id = args.arg_set_id
+        WHERE vc.id = 0
+        ORDER BY args.key
+        LIMIT 10;
+        """,
+        out=Csv("""
+        "key","display_value"
+        "package_name","com.google.android.apps.nexuslauncher"
+        "views[0].alpha","1.0"
+        "views[0].class_name","com.android.internal.policy.PhoneWindow@6cec234"
+        "views[0].hashcode","182652084"
+        "views[0].height","2400"
+        "views[0].parent_id","-1"
+        "views[0].scale_x","1.0"
+        "views[0].scale_y","1.0"
+        "views[0].view_id","NO_ID"
+        "views[0].width","1080"
+        """))
+
+  def test_handle_string_deinterning_errors(self):
+    return DiffTestBlueprint(
+        trace=Path('viewcapture.textproto'),
+        query="""
+        INCLUDE PERFETTO MODULE android.winscope.viewcapture;
+        SELECT
+          args.key, args.display_value
+        FROM
+          android_viewcapture AS vc JOIN args ON vc.arg_set_id = args.arg_set_id
+        WHERE vc.id = 1 and args.key = 'views[1].class_name';
+        """,
+        out=Csv("""
+        "key","display_value"
+        "views[1].class_name","STRING DE-INTERNING ERROR"
+        """))
diff --git a/test/trace_processor/diff_tests/parser/android/viewcapture.textproto b/test/trace_processor/diff_tests/parser/android/viewcapture.textproto
new file mode 100644
index 0000000..7f484b8
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/viewcapture.textproto
@@ -0,0 +1,128 @@
+packet {
+  clock_snapshot {
+    primary_trace_clock: BUILTIN_CLOCK_BOOTTIME
+    clocks {
+      clock_id: 6
+      timestamp: 448243204726
+    }
+    clocks {
+      clock_id: 2
+      timestamp: 1716366701256000218
+    }
+    clocks {
+      clock_id: 4
+      timestamp: 448237110366
+    }
+    clocks {
+      clock_id: 1
+      timestamp: 1716366701262094741
+    }
+    clocks {
+      clock_id: 3
+      timestamp: 448243204971
+    }
+    clocks {
+      clock_id: 5
+      timestamp: 448243205052
+    }
+  }
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+}
+
+packet {
+  first_packet_on_sequence: true
+  timestamp: 448881087865
+  winscope_extensions {
+    [perfetto.protos.WinscopeExtensionsImpl.viewcapture] {
+      package_name_iid: 1
+      window_name_iid: 1
+      views {
+        parent_id: -1
+        hashcode: 182652084
+        view_id_iid: 1
+        class_name_iid: 1
+        width: 1080
+        height: 2400
+        scale_x: 1.000000
+        scale_y: 1.000000
+        alpha: 1.000000
+        will_not_draw: true
+      }
+      views {
+        id: 1
+        hashcode: 130248925
+        view_id_iid: 3
+        class_name_iid: 2
+        width: 1080
+        height: 2400
+        scale_x: 1.000000
+        scale_y: 1.000000
+        alpha: 1.000000
+        will_not_draw: true
+      }
+    }
+  }
+  sequence_flags: 3
+  interned_data {
+    viewcapture_class_name {
+      iid: 1
+      str: "com.android.internal.policy.PhoneWindow@6cec234"
+    }
+    viewcapture_class_name {
+      iid: 2
+      str: "com.android.internal.policy.DecorView"
+    }
+    viewcapture_package_name {
+      iid: 1
+      str: "com.google.android.apps.nexuslauncher"
+    }
+    viewcapture_view_id {
+      iid: 1
+      str: "NO_ID"
+    }
+  }
+  trusted_uid: 10230
+  trusted_packet_sequence_id: 2
+  trusted_pid: 2688
+  previous_packet_dropped: true
+}
+
+packet {
+  timestamp: 448883575576
+  winscope_extensions {
+    [perfetto.protos.WinscopeExtensionsImpl.viewcapture] {
+      package_name_iid: 1
+      window_name_iid: 1
+      views {
+        parent_id: -1
+        hashcode: 182652084
+        view_id_iid: 1
+        class_name_iid: 1
+        width: 1080
+        height: 2400
+        scale_x: 1.000000
+        scale_y: 1.000000
+        alpha: 1.000000
+        will_not_draw: true
+      }
+      views {
+        id: 1
+        hashcode: 130248925
+        view_id_iid: 1
+        # triggers de-interning error because of unavailable iid-to-string mapping
+        class_name_iid: 3
+        width: 1080
+        height: 2400
+        scale_x: 1.000000
+        scale_y: 1.000000
+        alpha: 1.000000
+        will_not_draw: true
+      }
+    }
+  }
+  sequence_flags: 2
+  trusted_uid: 10230
+  trusted_packet_sequence_id: 2
+  trusted_pid: 2688
+}