Merge changes Icec4b762,I914a7262 into main

* changes:
  Trace Redaction - Implement thread merge strategy
  Trace Redaction - Expand Synth Threads to Synth Process
diff --git a/Android.bp b/Android.bp
index 5ce71eb..806c554 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12352,6 +12352,7 @@
     srcs: [
         "src/trace_processor/importers/proto/additional_modules.cc",
         "src/trace_processor/importers/proto/android_camera_event_module.cc",
+        "src/trace_processor/importers/proto/android_input_event_module.cc",
         "src/trace_processor/importers/proto/android_probes_module.cc",
         "src/trace_processor/importers/proto/android_probes_parser.cc",
         "src/trace_processor/importers/proto/android_probes_tracker.cc",
diff --git a/BUILD b/BUILD
index 3505021..17f6aef 100644
--- a/BUILD
+++ b/BUILD
@@ -1803,6 +1803,8 @@
         "src/trace_processor/importers/proto/additional_modules.h",
         "src/trace_processor/importers/proto/android_camera_event_module.cc",
         "src/trace_processor/importers/proto/android_camera_event_module.h",
+        "src/trace_processor/importers/proto/android_input_event_module.cc",
+        "src/trace_processor/importers/proto/android_input_event_module.h",
         "src/trace_processor/importers/proto/android_probes_module.cc",
         "src/trace_processor/importers/proto/android_probes_module.h",
         "src/trace_processor/importers/proto/android_probes_parser.cc",
diff --git a/include/perfetto/trace_processor/ref_counted.h b/include/perfetto/trace_processor/ref_counted.h
index bbe2c67..9a69399 100644
--- a/include/perfetto/trace_processor/ref_counted.h
+++ b/include/perfetto/trace_processor/ref_counted.h
@@ -144,8 +144,8 @@
     return !(*this == rhs);
   }
 
-  bool operator==(nullptr_t) const noexcept { return ptr_ == nullptr; }
-  bool operator!=(nullptr_t) const noexcept { return ptr_ != nullptr; }
+  bool operator==(std::nullptr_t) const noexcept { return ptr_ == nullptr; }
+  bool operator!=(std::nullptr_t) const noexcept { return ptr_ != nullptr; }
 
   T* get() const { return ptr_; }
   T* operator->() const { return ptr_; }
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index b16a231..70b0dee 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -330,14 +330,13 @@
   }
 
   static void Flush() {
-    Base::template Trace([](typename Base::TraceContext ctx) { ctx.Flush(); });
+    Base::Trace([](typename Base::TraceContext ctx) { ctx.Flush(); });
   }
 
   // Determine if *any* tracing category is enabled.
   static bool IsEnabled() {
     bool enabled = false;
-    Base::template CallIfEnabled(
-        [&](uint32_t /*instances*/) { enabled = true; });
+    Base::CallIfEnabled([&](uint32_t /*instances*/) { enabled = true; });
     return enabled;
   }
 
@@ -351,7 +350,7 @@
   static bool IsDynamicCategoryEnabled(
       const DynamicCategory& dynamic_category) {
     bool enabled = false;
-    Base::template Trace([&](typename Base::TraceContext ctx) {
+    Base::Trace([&](typename Base::TraceContext ctx) {
       enabled = enabled || IsDynamicCategoryEnabled(&ctx, dynamic_category);
     });
     return enabled;
@@ -498,7 +497,7 @@
                                  const protos::gen::TrackDescriptor& desc) {
     PERFETTO_DCHECK(track.uuid == desc.uuid());
     TrackRegistry::Get()->UpdateTrack(track, desc.SerializeAsString());
-    Base::template Trace([&](typename Base::TraceContext ctx) {
+    Base::Trace([&](typename Base::TraceContext ctx) {
       TrackEventInternal::WriteTrackDescriptor(
           track, ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
           *ctx.GetCustomTlsState(), TrackEventInternal::GetTraceTime());
@@ -1028,7 +1027,7 @@
                                  Lambda lambda) PERFETTO_ALWAYS_INLINE {
     using CatTraits = CategoryTraits<CategoryType>;
     if (CatTraits::kIsDynamic) {
-      Base::template TraceWithInstances(instances, std::move(lambda));
+      Base::TraceWithInstances(instances, std::move(lambda));
     } else {
       Base::template TraceWithInstances<CategoryTracePointTraits>(
           instances, std::move(lambda), {CatTraits::GetStaticIndex(category)});
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index e11a485..726b266 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -183,30 +183,6 @@
   PERFETTO_FATAL("For GCC");
 }
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-// Reports trace with `uuid` being finalized to the trace marker.
-//
-// This reimplements parts of android libcutils, because:
-// * libcutils is not exposed to the NDK and cannot be used from standalone
-//   perfetto
-// * libcutils atrace uses properties to enable tags, which are not required in
-//   this case.
-void ReportFinalizeTraceUuidToAtrace(const base::Uuid& uuid) {
-  base::ScopedFile file =
-      base::OpenFile("/sys/kernel/tracing/trace_marker", O_WRONLY);
-  if (!file) {
-    file = base::OpenFile("/sys/kernel/debug/tracing/trace_marker", O_WRONLY);
-    if (!file) {
-      return;
-    }
-  }
-  base::StackString<100> uuid_slice("N|%d|OtherTraces|finalize-uuid-%s",
-                                    base::GetProcessId(),
-                                    uuid.ToPrettyString().c_str());
-  PERFETTO_EINTR(write(file.get(), uuid_slice.c_str(), uuid_slice.len()));
-}
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-
 void ArgsAppend(std::string* str, const std::string& arg) {
   str->append(arg);
   str->append("\0", 1);
@@ -1288,12 +1264,6 @@
     }
   }
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-  // When multiple traces are being recorded at the same time, this is used to
-  // correlate one trace with another.
-  ReportFinalizeTraceUuidToAtrace(base::Uuid(uuid_));
-#endif
-
   tracing_succeeded_ = true;
   task_runner_.Quit();
 }
diff --git a/src/trace_processor/containers/interval_tree.h b/src/trace_processor/containers/interval_tree.h
index c083da0..64c8c92 100644
--- a/src/trace_processor/containers/interval_tree.h
+++ b/src/trace_processor/containers/interval_tree.h
@@ -17,117 +17,130 @@
 #ifndef SRC_TRACE_PROCESSOR_CONTAINERS_INTERVAL_TREE_H_
 #define SRC_TRACE_PROCESSOR_CONTAINERS_INTERVAL_TREE_H_
 
+#include <algorithm>
 #include <cstdint>
 #include <limits>
+#include <map>
 #include <memory>
+#include <optional>
+#include <set>
 #include <vector>
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/small_vector.h"
 
 namespace perfetto::trace_processor {
 
-// An implementation of an interval tree data structure, designed to efficiently
-// perform overlap queries on a set of intervals. Used by `interval_intersect`,
-// where one set of intervals (generally the bigger one) has interval tree
-// created based on it, as another queries `FindOverlaps` function for each
-// interval.
-// As interval tree is build on sorted (by `start`) set of N intervals, the
-// complexity of creating a tree goes down from O(N*logN) to O(N) and the
-// created tree is optimally balanced. Each call to `FindOverlaps` is O(logN).
+using Ts = uint64_t;
+using Id = uint32_t;
+
+// An implementation of a centered interval tree data structure, designed to
+// efficiently find all overlap queries on a set of intervals. Centered interval
+// tree has a build complexity of O(N*logN) and a query time of O(logN + k),
+// where k is the number of overlaps in the dataset.
 class IntervalTree {
  public:
+  // Maps to one trace processor slice.
   struct Interval {
-    uint32_t start;
-    uint32_t end;
-    uint32_t id;
+    Ts start;
+    Ts end;
+    Id id;
   };
 
-  // Takes vector of sorted intervals.
-  explicit IntervalTree(std::vector<Interval>& sorted_intervals) {
-    tree_root_ = BuildFromSortedIntervals(
-        sorted_intervals, 0, static_cast<int32_t>(sorted_intervals.size() - 1));
+  // Creates an interval tree from the vector of intervals.
+  explicit IntervalTree(const std::vector<Interval>& sorted_intervals) {
+    nodes_.reserve(sorted_intervals.size());
+    Node root_node(sorted_intervals.data(), sorted_intervals.size(), nodes_);
+    nodes_.emplace_back(std::move(root_node));
+    root_ = nodes_.size() - 1;
   }
 
-  // Modifies |overlaps| to contain ids of all intervals in the interval tree
-  // that overlap with |interval|.
-  void FindOverlaps(Interval interval, std::vector<uint32_t>& overlaps) const {
-    if (tree_root_) {
-      FindOverlaps(*tree_root_, interval, overlaps);
+  // Modifies |res| to contain Interval::Id of all intervals that overlap
+  // interval (s, e). Has a complexity of O(log(size of tree) + (number of
+  // overlaps)).
+  void FindOverlaps(uint64_t s, uint64_t e, std::vector<Id>& res) const {
+    std::vector<const Node*> stack{nodes_.data() + root_};
+    while (!stack.empty()) {
+      const Node* n = stack.back();
+      stack.pop_back();
+
+      for (const Interval& i : n->intervals_) {
+        // As we know that each interval overlaps the center, if the interval
+        // starts after the |end| we know [start,end] can't intersect the
+        // center.
+        if (i.start > e) {
+          break;
+        }
+
+        if (e > i.start && s < i.end) {
+          res.push_back(i.id);
+        }
+      }
+
+      if (e > n->center_ &&
+          n->right_node_ != std::numeric_limits<size_t>::max()) {
+        stack.push_back(&nodes_[n->right_node_]);
+      }
+      if (s < n->center_ &&
+          n->left_node_ != std::numeric_limits<size_t>::max()) {
+        stack.push_back(&nodes_[n->left_node_]);
+      }
     }
   }
 
  private:
   struct Node {
-    Interval interval;
-    uint32_t max;
-    std::unique_ptr<Node> left;
-    std::unique_ptr<Node> right;
+    base::SmallVector<Interval, 2> intervals_;
+    uint64_t center_ = 0;
+    size_t left_node_ = std::numeric_limits<size_t>::max();
+    size_t right_node_ = std::numeric_limits<size_t>::max();
 
-    explicit Node(Interval i) : interval(i), max(i.end) {}
+    explicit Node(const Interval* intervals,
+                  size_t intervals_size,
+                  std::vector<Node>& nodes) {
+      const Interval& mid_interval = intervals[intervals_size / 2];
+      center_ = (mid_interval.start + mid_interval.end) / 2;
+
+      // Find intervals that overlap the center_ and intervals that belong to
+      // the left node (finish before the center_). If an interval starts after
+      // the center break and assign all remining intervals to the right node.
+      // We can do this as the provided intervals are in sorted order.
+      std::vector<Interval> left;
+      for (uint32_t i = 0; i < intervals_size; i++) {
+        const Interval& inter = intervals[i];
+        // Starts after the center. As intervals are sorted on timestamp we
+        // know the rest of intervals will go to the right node.
+        if (inter.start > center_) {
+          Node n(intervals + i, intervals_size - i, nodes);
+          nodes.emplace_back(std::move(n));
+          right_node_ = nodes.size() - 1;
+          break;
+        }
+
+        // Finishes before the center.
+        if (inter.end < center_) {
+          left.push_back(intervals[i]);
+        } else {
+          // Overlaps the center.
+          intervals_.emplace_back(intervals[i]);
+        }
+      }
+
+      if (!left.empty()) {
+        Node n(left.data(), left.size(), nodes);
+        nodes.emplace_back(std::move(n));
+        left_node_ = nodes.size() - 1;
+      }
+    }
+
+    Node(const Node&) = delete;
+    Node& operator=(const Node&) = delete;
+
+    Node(Node&&) = default;
+    Node& operator=(Node&&) = default;
   };
 
-  static std::unique_ptr<Node> Insert(std::unique_ptr<Node> root, Interval i) {
-    if (root == nullptr) {
-      return std::make_unique<Node>(i);
-    }
-
-    if (i.start < root->interval.start) {
-      root->left = Insert(std::move(root->left), i);
-    } else {
-      root->right = Insert(std::move(root->right), i);
-    }
-
-    if (root->max < i.end) {
-      root->max = i.end;
-    }
-
-    return root;
-  }
-
-  static std::unique_ptr<Node> BuildFromSortedIntervals(
-      const std::vector<Interval>& is,
-      int32_t start,
-      int32_t end) {
-    // |start == end| happens if there is one element so we need to check for
-    // |start > end| that happens in the next recursive call.
-    if (start > end) {
-      return nullptr;
-    }
-
-    int32_t mid = start + (end - start) / 2;
-    auto node = std::make_unique<Node>(is[static_cast<uint32_t>(mid)]);
-
-    node->left = BuildFromSortedIntervals(is, start, mid - 1);
-    node->right = BuildFromSortedIntervals(is, mid + 1, end);
-
-    uint32_t max_from_children = std::max(
-        node->left ? node->left->max : std::numeric_limits<uint32_t>::min(),
-        node->right ? node->right->max : std::numeric_limits<uint32_t>::min());
-
-    node->max = std::max(node->interval.end, max_from_children);
-
-    return node;
-  }
-
-  static void FindOverlaps(const Node& node,
-                           const Interval& i,
-                           std::vector<uint32_t>& overlaps) {
-    // Intervals overlap if one starts before the other ends and ends after it
-    // starts.
-    if (node.interval.start < i.end && node.interval.end > i.start) {
-      overlaps.push_back(node.interval.id);
-    }
-
-    // Try to find overlaps with left.
-    if (i.start <= node.interval.start && node.left) {
-      FindOverlaps(*node.left, i, overlaps);
-    }
-
-    // Try to find overlaps with right.
-    if (i.start < node.max && node.right) {
-      FindOverlaps(*node.right, i, overlaps);
-    }
-  }
-
-  std::unique_ptr<Node> tree_root_;
+  size_t root_;
+  std::vector<Node> nodes_;
 };
 
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/containers/interval_tree_unittest.cc b/src/trace_processor/containers/interval_tree_unittest.cc
index 61023d4..ed4509b 100644
--- a/src/trace_processor/containers/interval_tree_unittest.cc
+++ b/src/trace_processor/containers/interval_tree_unittest.cc
@@ -54,7 +54,7 @@
   std::vector<Interval> interval({{10, 20, 5}});
   IntervalTree tree(interval);
   std::vector<uint32_t> overlaps;
-  tree.FindOverlaps({5, 30, 0}, overlaps);
+  tree.FindOverlaps(5, 30, overlaps);
 
   ASSERT_THAT(overlaps, UnorderedElementsAre(5));
 }
@@ -63,7 +63,7 @@
   auto intervals = CreateIntervals({{0, 10}, {5, 20}, {30, 40}});
   IntervalTree tree(intervals);
   std::vector<uint32_t> overlaps;
-  tree.FindOverlaps({4, 30, 0}, overlaps);
+  tree.FindOverlaps(4, 30, overlaps);
 
   ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1));
 }
@@ -74,13 +74,13 @@
   std::vector<uint32_t> overlaps;
 
   // Overlaps at the start point only
-  tree.FindOverlaps({10, 10, 0}, overlaps);
+  tree.FindOverlaps(10, 10, overlaps);
   ASSERT_THAT(overlaps, IsEmpty());
 
   overlaps.clear();
 
   // Overlaps at the end point only
-  tree.FindOverlaps({20, 20, 0}, overlaps);
+  tree.FindOverlaps(20, 20, overlaps);
   ASSERT_THAT(overlaps, IsEmpty());
 }
 
@@ -90,17 +90,17 @@
   std::vector<uint32_t> overlaps;
 
   // Before all intervals
-  tree.FindOverlaps({5, 9, 0}, overlaps);
+  tree.FindOverlaps(5, 9, overlaps);
   ASSERT_THAT(overlaps, IsEmpty());
   overlaps.clear();
 
   // Between intervals
-  tree.FindOverlaps({21, 29, 0}, overlaps);
+  tree.FindOverlaps(21, 29, overlaps);
   ASSERT_THAT(overlaps, IsEmpty());
   overlaps.clear();
 
   // After all intervals
-  tree.FindOverlaps({41, 50, 0}, overlaps);
+  tree.FindOverlaps(41, 50, overlaps);
   ASSERT_THAT(overlaps, IsEmpty());
 }
 
@@ -108,7 +108,7 @@
   auto intervals = CreateIntervals({{10, 20}, {10, 20}});
   IntervalTree tree(intervals);
   std::vector<uint32_t> overlaps;
-  tree.FindOverlaps({10, 20, 0}, overlaps);
+  tree.FindOverlaps(10, 20, overlaps);
   ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1));
 }
 
@@ -118,17 +118,17 @@
 
   std::vector<uint32_t> overlaps;
   /// Starts before, ends within
-  tree.FindOverlaps({9, 11, 0}, overlaps);
+  tree.FindOverlaps(9, 11, overlaps);
   ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1));
 
   overlaps.clear();
   // Starts within, ends within
-  tree.FindOverlaps({13, 21, 0}, overlaps);
-  ASSERT_THAT(overlaps, UnorderedElementsAre(1, 2));
+  tree.FindOverlaps(13, 21, overlaps);
+  ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1, 2));
 
   overlaps.clear();
   // Starts within, ends after
-  tree.FindOverlaps({18, 26, 0}, overlaps);
+  tree.FindOverlaps(18, 26, overlaps);
   ASSERT_THAT(overlaps, UnorderedElementsAre(1, 2, 3));
 }
 
@@ -137,7 +137,7 @@
   IntervalTree tree(intervals);
   std::vector<uint32_t> overlaps;
 
-  tree.FindOverlaps({19, 21, 0}, overlaps);
+  tree.FindOverlaps(19, 21, overlaps);
   ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1));
 }
 
@@ -153,10 +153,9 @@
         {prev_max, prev_max + (static_cast<uint32_t>(rng()) % 100)});
   }
   auto intervals = CreateIntervals(periods);
-  Interval query_i{periods.front().first, periods.back().first + 1, 5};
   IntervalTree tree(intervals);
   std::vector<uint32_t> overlaps;
-  tree.FindOverlaps(query_i, overlaps);
+  tree.FindOverlaps(periods.front().first, periods.back().first + 1, overlaps);
 
   EXPECT_EQ(overlaps.size(), kCount);
 }
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index 8b8d35c..e72b7c9 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -164,6 +164,21 @@
         id);
   }
 
+  BoundInserter AddArgsTo(tables::AndroidKeyEventsTable::Id id) {
+    return AddArgsTo(context_->storage->mutable_android_key_events_table(),
+                     id);
+  }
+
+  BoundInserter AddArgsTo(tables::AndroidMotionEventsTable::Id id) {
+    return AddArgsTo(context_->storage->mutable_android_motion_events_table(),
+                     id);
+  }
+
+  BoundInserter AddArgsTo(tables::AndroidInputEventDispatchTable::Id id) {
+    return AddArgsTo(
+        context_->storage->mutable_android_input_event_dispatch_table(), id);
+  }
+
   BoundInserter AddArgsTo(MetadataId id) {
     auto* table = context_->storage->mutable_metadata_table();
     uint32_t row = *table->id().IndexOf(id);
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index b92bed6..effda82 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -115,6 +115,8 @@
     "additional_modules.h",
     "android_camera_event_module.cc",
     "android_camera_event_module.h",
+    "android_input_event_module.cc",
+    "android_input_event_module.h",
     "android_probes_module.cc",
     "android_probes_module.h",
     "android_probes_parser.cc",
diff --git a/src/trace_processor/importers/proto/additional_modules.cc b/src/trace_processor/importers/proto/additional_modules.cc
index 91645c3..cf2ee84 100644
--- a/src/trace_processor/importers/proto/additional_modules.cc
+++ b/src/trace_processor/importers/proto/additional_modules.cc
@@ -18,6 +18,7 @@
 #include "src/trace_processor/importers/etw/etw_module_impl.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module_impl.h"
 #include "src/trace_processor/importers/proto/android_camera_event_module.h"
+#include "src/trace_processor/importers/proto/android_input_event_module.h"
 #include "src/trace_processor/importers/proto/android_probes_module.h"
 #include "src/trace_processor/importers/proto/graphics_event_module.h"
 #include "src/trace_processor/importers/proto/heap_graph_module.h"
@@ -45,6 +46,7 @@
   context->modules.emplace_back(new MetadataModule(context));
   context->modules.emplace_back(new V8Module(context));
   context->modules.emplace_back(new WinscopeModule(context));
+  context->modules.emplace_back(new AndroidInputEventModule(context));
 
   // Ftrace/Etw modules are special, because it has one extra method for parsing
   // ftrace/etw packets. So we need to store a pointer to it separately.
diff --git a/src/trace_processor/importers/proto/android_input_event_module.cc b/src/trace_processor/importers/proto/android_input_event_module.cc
new file mode 100644
index 0000000..26671a7
--- /dev/null
+++ b/src/trace_processor/importers/proto/android_input_event_module.cc
@@ -0,0 +1,147 @@
+/*
+ * 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/android_input_event_module.h"
+
+#include "protos/perfetto/trace/android/android_input_event.pbzero.h"
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/proto/args_parser.h"
+#include "src/trace_processor/importers/proto/trace.descriptor.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/android_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto::trace_processor {
+
+using perfetto::protos::pbzero::AndroidInputEvent;
+using perfetto::protos::pbzero::AndroidKeyEvent;
+using perfetto::protos::pbzero::AndroidMotionEvent;
+using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent;
+using perfetto::protos::pbzero::TracePacket;
+
+AndroidInputEventModule::AndroidInputEventModule(TraceProcessorContext* context)
+    : context_(*context), args_parser_(pool_) {
+  pool_.AddFromFileDescriptorSet(kTraceDescriptor.data(),
+                                 kTraceDescriptor.size());
+  RegisterForField(TracePacket::kAndroidInputEventFieldNumber, context);
+}
+
+void AndroidInputEventModule::ParseTracePacketData(
+    const TracePacket::Decoder& decoder,
+    int64_t packet_ts,
+    const TracePacketData&,
+    uint32_t field_id) {
+  if (field_id != TracePacket::kAndroidInputEventFieldNumber)
+    return;
+
+  auto input_event = AndroidInputEvent::Decoder(decoder.android_input_event());
+
+  constexpr static auto supported_fields = std::array{
+      AndroidInputEvent::kDispatcherMotionEventFieldNumber,
+      AndroidInputEvent::kDispatcherMotionEventRedactedFieldNumber,
+      AndroidInputEvent::kDispatcherKeyEventFieldNumber,
+      AndroidInputEvent::kDispatcherKeyEventRedactedFieldNumber,
+      AndroidInputEvent::kDispatcherWindowDispatchEventFieldNumber,
+      AndroidInputEvent::kDispatcherWindowDispatchEventRedactedFieldNumber};
+
+  for (auto sub_field_id : supported_fields) {
+    auto sub_field = input_event.Get(static_cast<uint32_t>(sub_field_id));
+    if (!sub_field.valid())
+      continue;
+
+    switch (sub_field_id) {
+      case AndroidInputEvent::kDispatcherMotionEventFieldNumber:
+      case AndroidInputEvent::kDispatcherMotionEventRedactedFieldNumber:
+        ParseMotionEvent(packet_ts, sub_field.as_bytes());
+        return;
+      case AndroidInputEvent::kDispatcherKeyEventFieldNumber:
+      case AndroidInputEvent::kDispatcherKeyEventRedactedFieldNumber:
+        ParseKeyEvent(packet_ts, sub_field.as_bytes());
+        return;
+      case AndroidInputEvent::kDispatcherWindowDispatchEventFieldNumber:
+      case AndroidInputEvent::kDispatcherWindowDispatchEventRedactedFieldNumber:
+        ParseWindowDispatchEvent(packet_ts, sub_field.as_bytes());
+        return;
+    }
+  }
+}
+
+void AndroidInputEventModule::ParseMotionEvent(
+    int64_t packet_ts,
+    const protozero::ConstBytes& bytes) {
+  AndroidMotionEvent::Decoder event_proto(bytes);
+  tables::AndroidMotionEventsTable::Row event_row;
+  event_row.event_id = event_proto.event_id();
+  event_row.ts = event_proto.event_time_nanos();
+
+  auto event_row_id = context_.storage->mutable_android_motion_events_table()
+                          ->Insert(event_row)
+                          .id;
+  auto inserter = context_.args_tracker->AddArgsTo(event_row_id);
+  ArgsParser writer{packet_ts, inserter, *context_.storage};
+
+  base::Status status =
+      args_parser_.ParseMessage(bytes, ".perfetto.protos.AndroidMotionEvent",
+                                nullptr /*parse all fields*/, writer);
+  if (!status.ok())
+    context_.storage->IncrementStats(stats::android_input_event_parse_errors);
+}
+
+void AndroidInputEventModule::ParseKeyEvent(
+    int64_t packet_ts,
+    const protozero::ConstBytes& bytes) {
+  AndroidKeyEvent::Decoder event_proto(bytes);
+  tables::AndroidKeyEventsTable::Row event_row;
+  event_row.event_id = event_proto.event_id();
+  event_row.ts = event_proto.event_time_nanos();
+
+  auto event_row_id = context_.storage->mutable_android_key_events_table()
+                          ->Insert(event_row)
+                          .id;
+  auto inserter = context_.args_tracker->AddArgsTo(event_row_id);
+  ArgsParser writer{packet_ts, inserter, *context_.storage};
+
+  base::Status status =
+      args_parser_.ParseMessage(bytes, ".perfetto.protos.AndroidKeyEvent",
+                                nullptr /*parse all fields*/, writer);
+  if (!status.ok())
+    context_.storage->IncrementStats(stats::android_input_event_parse_errors);
+}
+
+void AndroidInputEventModule::ParseWindowDispatchEvent(
+    int64_t packet_ts,
+    const protozero::ConstBytes& bytes) {
+  AndroidWindowInputDispatchEvent::Decoder event_proto(bytes);
+  tables::AndroidInputEventDispatchTable::Row event_row;
+  event_row.event_id = event_proto.event_id();
+  event_row.vsync_id = event_proto.vsync_id();
+  event_row.window_id = event_proto.window_id();
+
+  auto event_row_id =
+      context_.storage->mutable_android_input_event_dispatch_table()
+          ->Insert(event_row)
+          .id;
+  auto inserter = context_.args_tracker->AddArgsTo(event_row_id);
+  ArgsParser writer{packet_ts, inserter, *context_.storage};
+
+  base::Status status = args_parser_.ParseMessage(
+      bytes, ".perfetto.protos.AndroidWindowInputDispatchEvent",
+      nullptr /*parse all fields*/, writer);
+  if (!status.ok())
+    context_.storage->IncrementStats(stats::android_input_event_parse_errors);
+}
+
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/android_input_event_module.h b/src/trace_processor/importers/proto/android_input_event_module.h
new file mode 100644
index 0000000..c310909
--- /dev/null
+++ b/src/trace_processor/importers/proto/android_input_event_module.h
@@ -0,0 +1,51 @@
+/*
+ * 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_ANDROID_INPUT_EVENT_MODULE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ANDROID_INPUT_EVENT_MODULE_H_
+
+#include <cstdint>
+#include "perfetto/base/build_config.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_processor/importers/common/parser_types.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/util/proto_to_args_parser.h"
+
+namespace perfetto::trace_processor {
+
+class AndroidInputEventModule : public ProtoImporterModule {
+ public:
+  explicit AndroidInputEventModule(TraceProcessorContext* context);
+
+  void ParseTracePacketData(const protos::pbzero::TracePacket::Decoder&,
+                            int64_t packet_ts,
+                            const TracePacketData&,
+                            uint32_t field_id) override;
+
+ private:
+  TraceProcessorContext& context_;
+  DescriptorPool pool_;
+  util::ProtoToArgsParser args_parser_;
+
+  void ParseMotionEvent(int64_t packet_ts, const protozero::ConstBytes& bytes);
+  void ParseKeyEvent(int64_t packet_ts, const protozero::ConstBytes& bytes);
+  void ParseWindowDispatchEvent(int64_t packet_ts, const protozero::ConstBytes& bytes);
+};
+
+}  // namespace perfetto::trace_processor
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ANDROID_INPUT_EVENT_MODULE_H_
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
index d958c0b..7f8277f 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.cc
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -233,6 +233,13 @@
         return util::OkStatus();
       }
       timestamp = trace_ts.value();
+      // TODO(lalitm): Ideally this guard is implemented in TraceSorter
+      // to allow for checking of other trace formats
+      if (timestamp < 0) {
+        return base::ErrStatus("Failed due to negative timestamp, %" PRId64
+                               "is negative.",
+                               timestamp);
+      }
     }
   } else {
     timestamp = std::max(latest_timestamp_, context_->sorter->max_timestamp());
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc
index feaa228..82ed018 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc
@@ -16,18 +16,84 @@
 
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
 
+#include <cstdint>
 #include <optional>
+#include <string>
+#include <unordered_map>
 #include <unordered_set>
 #include <utility>
 
+#include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_tokenizer.h"
 #include "src/trace_processor/util/status_macros.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
+namespace {
+
+base::Status ErrorAtToken(const SqliteTokenizer& tokenizer,
+                          const SqliteTokenizer::Token& token,
+                          const char* error) {
+  std::string traceback = tokenizer.AsTraceback(token);
+  return base::ErrStatus("%s%s", traceback.c_str(), error);
+}
+
+struct InvocationArg {
+  std::optional<SqlSource> arg;
+  bool has_more;
+};
+
+base::StatusOr<InvocationArg> ParseMacroInvocationArg(
+    SqliteTokenizer& tokenizer,
+    SqliteTokenizer::Token& tok,
+    bool has_prev_args) {
+  uint32_t nested_parens = 0;
+  bool seen_token_in_arg = false;
+  auto start = tokenizer.NextNonWhitespace();
+  for (tok = start;; tok = tokenizer.NextNonWhitespace()) {
+    if (tok.IsTerminal()) {
+      if (tok.token_type == SqliteTokenType::TK_SEMI) {
+        // TODO(b/290185551): add a link to macro documentation.
+        return ErrorAtToken(tokenizer, tok,
+                            "Semi-colon is not allowed in macro invocation");
+      }
+      // TODO(b/290185551): add a link to macro documentation.
+      return ErrorAtToken(tokenizer, tok, "Macro invocation not complete");
+    }
+
+    bool is_arg_terminator = tok.token_type == SqliteTokenType::TK_RP ||
+                             tok.token_type == SqliteTokenType::TK_COMMA;
+    if (nested_parens == 0 && is_arg_terminator) {
+      bool token_required =
+          has_prev_args || tok.token_type != SqliteTokenType::TK_RP;
+      if (!seen_token_in_arg && token_required) {
+        // TODO(b/290185551): add a link to macro documentation.
+        return ErrorAtToken(tokenizer, tok, "Macro arg is empty");
+      }
+      return InvocationArg{
+          seen_token_in_arg ? std::make_optional(tokenizer.Substr(start, tok))
+                            : std::optional<SqlSource>(std::nullopt),
+          tok.token_type == SqliteTokenType::TK_COMMA,
+      };
+    }
+    seen_token_in_arg = true;
+
+    if (tok.token_type == SqliteTokenType::TK_LP) {
+      nested_parens++;
+      continue;
+    }
+    if (tok.token_type == SqliteTokenType::TK_RP) {
+      nested_parens--;
+      continue;
+    }
+  }
+}
+
+}  // namespace
 
 PerfettoSqlPreprocessor::PerfettoSqlPreprocessor(
     SqlSource source,
@@ -165,62 +231,7 @@
                         "Macro invoked with too few args");
   }
   PERFETTO_CHECK(inner_bindings.size() == macro->args.size());
-  return MacroInvocation{macro, inner_bindings};
+  return MacroInvocation{macro, std::move(inner_bindings)};
 }
 
-base::StatusOr<PerfettoSqlPreprocessor::InvocationArg>
-PerfettoSqlPreprocessor::ParseMacroInvocationArg(SqliteTokenizer& tokenizer,
-                                                 SqliteTokenizer::Token& tok,
-                                                 bool has_prev_args) {
-  uint32_t nested_parens = 0;
-  bool seen_token_in_arg = false;
-  auto start = tokenizer.NextNonWhitespace();
-  for (tok = start;; tok = tokenizer.NextNonWhitespace()) {
-    if (tok.IsTerminal()) {
-      if (tok.token_type == SqliteTokenType::TK_SEMI) {
-        // TODO(b/290185551): add a link to macro documentation.
-        return ErrorAtToken(tokenizer, tok,
-                            "Semi-colon is not allowed in macro invocation");
-      }
-      // TODO(b/290185551): add a link to macro documentation.
-      return ErrorAtToken(tokenizer, tok, "Macro invocation not complete");
-    }
-
-    bool is_arg_terminator = tok.token_type == SqliteTokenType::TK_RP ||
-                             tok.token_type == SqliteTokenType::TK_COMMA;
-    if (nested_parens == 0 && is_arg_terminator) {
-      bool token_required =
-          has_prev_args || tok.token_type != SqliteTokenType::TK_RP;
-      if (!seen_token_in_arg && token_required) {
-        // TODO(b/290185551): add a link to macro documentation.
-        return ErrorAtToken(tokenizer, tok, "Macro arg is empty");
-      }
-      return InvocationArg{
-          seen_token_in_arg ? std::make_optional(tokenizer.Substr(start, tok))
-                            : std::optional<SqlSource>(std::nullopt),
-          tok.token_type == SqliteTokenType::TK_COMMA,
-      };
-    }
-    seen_token_in_arg = true;
-
-    if (tok.token_type == SqliteTokenType::TK_LP) {
-      nested_parens++;
-      continue;
-    }
-    if (tok.token_type == SqliteTokenType::TK_RP) {
-      nested_parens--;
-      continue;
-    }
-  }
-}
-
-base::Status PerfettoSqlPreprocessor::ErrorAtToken(
-    const SqliteTokenizer& tokenizer,
-    const SqliteTokenizer::Token& token,
-    const char* error) {
-  std::string traceback = tokenizer.AsTraceback(token);
-  return base::ErrStatus("%s%s", traceback.c_str(), error);
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h
index a1d15e3..17681de 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h
@@ -23,13 +23,13 @@
 #include <unordered_set>
 #include <vector>
 
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/status_or.h"
 #include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_tokenizer.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 // Preprocessor for PerfettoSQL statements. The main responsiblity of this
 // class is to perform similar functions to the C/C++ preprocessor (e.g.
@@ -72,14 +72,7 @@
     const Macro* macro;
     std::unordered_map<std::string, SqlSource> arg_bindings;
   };
-  struct InvocationArg {
-    std::optional<SqlSource> arg;
-    bool has_more;
-  };
 
-  base::Status ErrorAtToken(const SqliteTokenizer& tokenizer,
-                            const SqliteTokenizer::Token& token,
-                            const char* error);
   base::StatusOr<SqlSource> RewriteInternal(
       const SqlSource&,
       const std::unordered_map<std::string, SqlSource>& arg_bindings);
@@ -89,10 +82,6 @@
       SqliteTokenizer::Token& token,
       const SqliteTokenizer::Token& name_token,
       const std::unordered_map<std::string, SqlSource>& arg_bindings);
-  base::StatusOr<InvocationArg> ParseMacroInvocationArg(
-      SqliteTokenizer& tokenizer,
-      SqliteTokenizer::Token& token,
-      bool has_prev_args);
 
   SqliteTokenizer global_tokenizer_;
   const base::FlatHashMap<std::string, Macro>* macros_ = nullptr;
@@ -101,7 +90,6 @@
   base::Status status_;
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_PREPROCESSOR_H_
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/input.sql b/src/trace_processor/perfetto_sql/stdlib/android/input.sql
index a1d7b11..5ac6c74 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/input.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/input.sql
@@ -130,3 +130,62 @@
     AND dispatch.event_seq = finish.event_seq
 JOIN (SELECT * FROM _input_message_received WHERE event_type = '0x2') finish_ack
   ON finish_ack.event_channel = dispatch.event_channel AND dispatch.event_seq = finish_ack.event_seq;
+
+-- Key events processed by the Android framework (from android.input.inputevent data source).
+CREATE PERFETTO VIEW android_key_events(
+  -- ID of the trace entry
+  id INT,
+  -- The randomly-generated ID associated with each input event processed
+  -- by Android Framework, used to track the event through the input pipeline
+  event_id INT,
+  -- The timestamp associated with the input event
+  ts INT,
+  -- Details of the input event parsed from the proto message
+  arg_set_id INT
+) AS
+SELECT
+  id,
+  event_id,
+  ts,
+  arg_set_id
+FROM __intrinsic_android_key_events;
+
+-- Motion events processed by the Android framework (from android.input.inputevent data source).
+CREATE PERFETTO VIEW android_motion_events(
+  -- ID of the trace entry
+  id INT,
+  -- The randomly-generated ID associated with each input event processed
+  -- by Android Framework, used to track the event through the input pipeline
+  event_id INT,
+  -- The timestamp associated with the input event
+  ts INT,
+  -- Details of the input event parsed from the proto message
+  arg_set_id INT
+) AS
+SELECT
+  id,
+  event_id,
+  ts,
+  arg_set_id
+FROM __intrinsic_android_motion_events;
+
+-- Input event dispatching information in Android (from android.input.inputevent data source).
+CREATE PERFETTO VIEW android_input_event_dispatch(
+  -- ID of the trace entry
+  id INT,
+  -- Event ID of the input event that was dispatched
+  event_id INT,
+  -- Extra args parsed from the proto message
+  arg_set_id INT,
+  -- Vsync ID that identifies the state of the windows during which the dispatch decision was made
+  vsync_id INT,
+  -- Window ID of the window receiving the event
+  window_id INT
+) AS
+SELECT
+  id,
+  event_id,
+  arg_set_id,
+  vsync_id,
+  window_id
+FROM __intrinsic_android_input_event_dispatch;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
index 6630c53..f9b018c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
@@ -22,7 +22,8 @@
     ts INT,
     -- Power rail name. From `counter_track.name`.
     power_rail_name INT,
-    -- Power rails counter value in micro watts.
+    -- The energy accumulated by this rail since boot in microwatt-seconds
+    -- (uWs) (AKA micro-joules).
     value DOUBLE
 )
 AS
@@ -33,4 +34,4 @@
     c.value
 FROM counter c
 JOIN counter_track t ON c.track_id = t.id
-WHERE name GLOB 'power.*';
\ No newline at end of file
+WHERE name GLOB 'power.*';
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 6238455..057c2fa 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -363,7 +363,11 @@
   F(ftrace_missing_event_id,              kSingle,  kInfo,    kAnalysis,       \
       "Indicates that the ftrace event was dropped because the event id was "  \
       "missing. This is an 'info' stat rather than an error stat because "     \
-      "this can be legitimately missing due to proto filtering.")
+      "this can be legitimately missing due to proto filtering."),             \
+  F(android_input_event_parse_errors,     kSingle,  kInfo,     kAnalysis,      \
+      "Android input event packet has unknown fields, which results "          \
+      "in some arguments missing. You may need a newer version of trace "      \
+      "processor to parse them.")
 // clang-format on
 
 enum Type {
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 734fd0d..61da6d9 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -514,6 +514,29 @@
     return &android_dumpstate_table_;
   }
 
+  const tables::AndroidKeyEventsTable& android_key_events_table() const {
+    return android_key_events_table_;
+  }
+  tables::AndroidKeyEventsTable* mutable_android_key_events_table() {
+    return &android_key_events_table_;
+  }
+
+  const tables::AndroidMotionEventsTable& android_motion_events_table() const {
+    return android_motion_events_table_;
+  }
+  tables::AndroidMotionEventsTable* mutable_android_motion_events_table() {
+    return &android_motion_events_table_;
+  }
+
+  const tables::AndroidInputEventDispatchTable&
+  android_input_event_dispatch_table() const {
+    return android_input_event_dispatch_table_;
+  }
+  tables::AndroidInputEventDispatchTable*
+  mutable_android_input_event_dispatch_table() {
+    return &android_input_event_dispatch_table_;
+  }
+
   const StatsMap& stats() const { return stats_; }
 
   const tables::MetadataTable& metadata_table() const {
@@ -1097,6 +1120,11 @@
 
   tables::AndroidDumpstateTable android_dumpstate_table_{&string_pool_};
 
+  tables::AndroidKeyEventsTable android_key_events_table_{&string_pool_};
+  tables::AndroidMotionEventsTable android_motion_events_table_{&string_pool_};
+  tables::AndroidInputEventDispatchTable
+      android_input_event_dispatch_table_{&string_pool_};
+
   tables::StackProfileMappingTable stack_profile_mapping_table_{&string_pool_};
   tables::StackProfileFrameTable stack_profile_frame_table_{&string_pool_};
   tables::StackProfileCallsiteTable stack_profile_callsite_table_{
diff --git a/src/trace_processor/tables/android_tables.py b/src/trace_processor/tables/android_tables.py
index 88f2592..ba200ef 100644
--- a/src/trace_processor/tables/android_tables.py
+++ b/src/trace_processor/tables/android_tables.py
@@ -14,11 +14,11 @@
 """Contains tables for relevant for Android."""
 
 from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnDoc
 from python.generators.trace_processor_table.public import CppDouble
 from python.generators.trace_processor_table.public import CppInt32
 from python.generators.trace_processor_table.public import CppInt64
 from python.generators.trace_processor_table.public import CppOptional
-from python.generators.trace_processor_table.public import CppSelfTableId
 from python.generators.trace_processor_table.public import CppString
 from python.generators.trace_processor_table.public import Table
 from python.generators.trace_processor_table.public import TableDoc
@@ -156,9 +156,100 @@
                 '''
         }))
 
+ANDROID_MOTION_EVENTS_TABLE = Table(
+    python_module=__file__,
+    class_name='AndroidMotionEventsTable',
+    sql_name='__intrinsic_android_motion_events',
+    columns=[
+        C('event_id', CppUint32()),
+        C('ts', CppInt64()),
+        C('arg_set_id', CppUint32()),
+    ],
+    tabledoc=TableDoc(
+        doc='Contains Android MotionEvents processed by the system',
+        group='Android',
+        columns={
+            'event_id':
+                '''
+                    The randomly-generated ID associated with each input event processed
+                    by Android Framework, used to track the event through the input pipeline.
+                ''',
+            'ts':
+                '''The timestamp associated with the input event.''',
+            'arg_set_id':
+                ColumnDoc(
+                    doc='Details of the motion event parsed from the proto message.',
+                    joinable='args.arg_set_id'),
+        }))
+
+ANDROID_KEY_EVENTS_TABLE = Table(
+    python_module=__file__,
+    class_name='AndroidKeyEventsTable',
+    sql_name='__intrinsic_android_key_events',
+    columns=[
+        C('event_id', CppUint32()),
+        C('ts', CppInt64()),
+        C('arg_set_id', CppUint32()),
+    ],
+    tabledoc=TableDoc(
+        doc='Contains Android KeyEvents processed by the system',
+        group='Android',
+        columns={
+            'event_id':
+                '''
+                    The randomly-generated ID associated with each input event processed
+                    by Android Framework, used to track the event through the input pipeline.
+                ''',
+            'ts':
+                '''The timestamp associated with the input event.''',
+            'arg_set_id':
+                ColumnDoc(
+                    doc='Details of the key event parsed from the proto message.',
+                    joinable='args.arg_set_id'),
+        }))
+
+ANDROID_INPUT_EVENT_DISPATCH_TABLE = Table(
+    python_module=__file__,
+    class_name='AndroidInputEventDispatchTable',
+    sql_name='__intrinsic_android_input_event_dispatch',
+    columns=[
+        C('event_id', CppUint32()),
+        C('arg_set_id', CppUint32()),
+        C('vsync_id', CppInt64()),
+        C('window_id', CppInt32()),
+    ],
+    tabledoc=TableDoc(
+        doc=
+            '''
+                Contains records of Android input events being dispatched to input windows
+                by the Android Framework.
+            ''',
+        group='Android',
+        columns={
+            'event_id':
+                ColumnDoc(
+                    doc='The id of the input event that was dispatched.',
+                    joinable='__intrinsic_android_motion_events.event_id'),
+            'arg_set_id':
+                ColumnDoc(
+                    doc='Details of the dispatched event parsed from the proto message.',
+                    joinable='args.arg_set_id'),
+            'vsync_id':
+                '''
+                    The id of the vsync during which the Framework made the decision to
+                    dispatch this input event, used to identify the state of the input windows
+                    when the dispatching decision was made.
+                ''',
+            'window_id':
+                'The id of the window to which the event was dispatched.',
+        }))
+
 # Keep this list sorted.
 ALL_TABLES = [
     ANDROID_LOG_TABLE,
     ANDROID_DUMPSTATE_TABLE,
     ANDROID_GAME_INTERVENTION_LIST_TABLE,
+    ANDROID_KEY_EVENTS_TABLE,
+    ANDROID_MOTION_EVENTS_TABLE,
+    ANDROID_INPUT_EVENT_DISPATCH_TABLE,
 ]
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 8339b48..067cefa 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -40,6 +40,9 @@
 AndroidDumpstateTable::~AndroidDumpstateTable() = default;
 AndroidGameInterventionListTable::~AndroidGameInterventionListTable() = default;
 AndroidLogTable::~AndroidLogTable() = default;
+AndroidKeyEventsTable::~AndroidKeyEventsTable() = default;
+AndroidMotionEventsTable::~AndroidMotionEventsTable() = default;
+AndroidInputEventDispatchTable::~AndroidInputEventDispatchTable() = default;
 
 // counter_tables_py.h
 CounterTable::~CounterTable() = default;
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index 731f4e3..b466399 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -811,5 +811,11 @@
   ASSERT_THAT(it.Status().message(), HasSubstr("duplicate_b"));
 }
 
+TEST_F(TraceProcessorIntegrationTest, NegativeTimestampError) {
+  base::Status status = LoadTrace("trace_with_negative_timestamp", 4096);
+  ASSERT_FALSE(status.ok());
+  ASSERT_THAT(status.message(), HasSubstr("negative timestamp"));
+}
+
 }  // namespace
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 7eac7b7..00f87bf 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -865,6 +865,9 @@
   RegisterStaticTable(storage->android_log_table());
   RegisterStaticTable(storage->android_dumpstate_table());
   RegisterStaticTable(storage->android_game_intervention_list_table());
+  RegisterStaticTable(storage->android_key_events_table());
+  RegisterStaticTable(storage->android_motion_events_table());
+  RegisterStaticTable(storage->android_input_event_dispatch_table());
 
   RegisterStaticTable(storage->vulkan_memory_allocations_table());
 
diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc
index 6631d68..0a06023 100644
--- a/src/tracing/service/tracing_service_impl.cc
+++ b/src/tracing/service/tracing_service_impl.cc
@@ -496,8 +496,6 @@
   PERFETTO_DLOG("Consumer %p disconnected", reinterpret_cast<void*>(consumer));
   PERFETTO_DCHECK(consumers_.count(consumer));
 
-  // TODO(primiano) : Check that this is safe (what happens if there are
-  // ReadBuffers() calls posted in the meantime? They need to become noop).
   if (consumer->tracing_session_id_)
     FreeBuffers(consumer->tracing_session_id_);  // Will also DisableTracing().
   consumers_.erase(consumer);
@@ -2644,10 +2642,23 @@
   bool is_long_trace =
       (tracing_session->config.write_into_file() &&
        tracing_session->config.file_write_period_ms() < kMillisPerDay);
+  auto pending_clones = std::move(tracing_session->pending_clones);
   tracing_sessions_.erase(tsid);
   tracing_session = nullptr;
   UpdateMemoryGuardrail();
 
+  for (const auto& id_to_clone_op : pending_clones) {
+    const PendingClone& clone_op = id_to_clone_op.second;
+    if (clone_op.weak_consumer) {
+      task_runner_->PostTask([weak_consumer = clone_op.weak_consumer] {
+        if (weak_consumer) {
+          weak_consumer->consumer_->OnSessionCloned(
+              {false, "Original session ended", {}});
+        }
+      });
+    }
+  }
+
   PERFETTO_LOG("Tracing session %" PRIu64 " ended, total sessions:%zu", tsid,
                tracing_sessions_.size());
 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) && \
@@ -3217,9 +3228,9 @@
   // Sum up all the cloned traced buffers.
   for (const auto& id_to_ts : tracing_sessions_) {
     const TracingSession& ts = id_to_ts.second;
-    for (const auto& id_to_pending_clone : ts.pending_clones) {
-      const PendingClone& pending_clone = id_to_pending_clone.second;
-      for (const std::unique_ptr<TraceBuffer>& buf : pending_clone.buffers) {
+    for (const auto& id_to_clone_op : ts.pending_clones) {
+      const PendingClone& clone_op = id_to_clone_op.second;
+      for (const std::unique_ptr<TraceBuffer>& buf : clone_op.buffers) {
         if (buf) {
           total_buffer_bytes += buf->size();
         }
@@ -3709,10 +3720,11 @@
   return trigger_count;
 }
 
-void TracingServiceImpl::FlushAndCloneSession(ConsumerEndpointImpl* consumer,
-                                              TracingSessionID tsid,
-                                              bool skip_trace_filter,
-                                              bool for_bugreport) {
+base::Status TracingServiceImpl::FlushAndCloneSession(
+    ConsumerEndpointImpl* consumer,
+    TracingSessionID tsid,
+    bool skip_trace_filter,
+    bool for_bugreport) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   auto clone_target = FlushFlags::CloneTarget::kUnknown;
 
@@ -3725,9 +3737,8 @@
     PERFETTO_LOG("Looking for sessions for bugreport");
     TracingSession* session = FindTracingSessionWithMaxBugreportScore();
     if (!session) {
-      consumer->consumer_->OnSessionCloned(
-          {false, "No tracing sessions eligible for bugreport found", {}});
-      return;
+      return base::ErrStatus(
+          "No tracing sessions eligible for bugreport found");
     }
     tsid = session->id;
     clone_target = FlushFlags::CloneTarget::kBugreport;
@@ -3739,9 +3750,14 @@
 
   TracingSession* session = GetTracingSession(tsid);
   if (!session) {
-    consumer->consumer_->OnSessionCloned(
-        {false, "Tracing session not found", {}});
-    return;
+    return base::ErrStatus("Tracing session not found");
+  }
+
+  // Skip the UID check for sessions marked with a bugreport_score > 0.
+  // Those sessions, by design, can be stolen by any other consumer for the
+  // sake of creating snapshots for bugreports.
+  if (!session->IsCloneAllowed(consumer->uid_)) {
+    return PERFETTO_SVC_ERR("Not allowed to clone a session from another UID");
   }
 
   // If any of the buffers are marked as clear_before_clone, reset them before
@@ -3773,9 +3789,8 @@
       // We cannot leave the original tracing session buffer-less as it would
       // cause crashes when data sources commit new data.
       buf = std::move(old_buf);
-      consumer->consumer_->OnSessionCloned(
-          {false, "Buffer allocation failed while attempting to clone", {}});
-      return;
+      return base::ErrStatus(
+          "Buffer allocation failed while attempting to clone");
     }
   }
 
@@ -3821,6 +3836,8 @@
         FlushFlags(FlushFlags::Initiator::kTraced,
                    FlushFlags::Reason::kTraceClone, clone_target));
   }
+
+  return base::OkStatus();
 }
 
 std::map<ProducerID, std::vector<DataSourceInstanceID>>
@@ -3958,13 +3975,6 @@
         "The consumer is already attached to another tracing session");
   }
 
-  // Skip the UID check for sessions marked with a bugreport_score > 0.
-  // Those sessions, by design, can be stolen by any other consumer for the
-  // sake of creating snapshots for bugreports.
-  if (!src->IsCloneAllowed(consumer->uid_)) {
-    return PERFETTO_SVC_ERR("Not allowed to clone a session from another UID");
-  }
-
   std::vector<BufferID> buf_ids =
       buffer_ids_.AllocateMultiple(buf_snaps.size());
   if (buf_ids.size() != buf_snaps.size()) {
@@ -4405,8 +4415,12 @@
     CloneSessionArgs args) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   // FlushAndCloneSession will call OnSessionCloned after the async flush.
-  service_->FlushAndCloneSession(this, tsid, args.skip_trace_filter,
-                                 args.for_bugreport);
+  base::Status result = service_->FlushAndCloneSession(
+      this, tsid, args.skip_trace_filter, args.for_bugreport);
+
+  if (!result.ok()) {
+    consumer_->OnSessionCloned({false, result.message(), {}});
+  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tracing/service/tracing_service_impl.h b/src/tracing/service/tracing_service_impl.h
index 1dbcbe6..64b1f50 100644
--- a/src/tracing/service/tracing_service_impl.h
+++ b/src/tracing/service/tracing_service_impl.h
@@ -352,10 +352,10 @@
              ConsumerEndpoint::FlushCallback,
              FlushFlags);
   void FlushAndDisableTracing(TracingSessionID);
-  void FlushAndCloneSession(ConsumerEndpointImpl*,
-                            TracingSessionID,
-                            bool skip_filter,
-                            bool for_bugreport);
+  base::Status FlushAndCloneSession(ConsumerEndpointImpl*,
+                                    TracingSessionID,
+                                    bool skip_filter,
+                                    bool for_bugreport);
 
   // Starts reading the internal tracing buffers from the tracing session `tsid`
   // and sends them to `*consumer` (which must be != nullptr).
diff --git a/src/tracing/service/tracing_service_impl_unittest.cc b/src/tracing/service/tracing_service_impl_unittest.cc
index 9e54d57..e512f7b 100644
--- a/src/tracing/service/tracing_service_impl_unittest.cc
+++ b/src/tracing/service/tracing_service_impl_unittest.cc
@@ -5221,7 +5221,7 @@
 }
 
 TEST_F(TracingServiceImplTest, CloneConsumerDisconnect) {
-  // The consumer the creates the initial tracing session.
+  // The consumer that creates the initial tracing session.
   std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
   consumer->Connect(svc.get());
 
@@ -5276,6 +5276,71 @@
   consumer->WaitForTracingDisabled();
 }
 
+TEST_F(TracingServiceImplTest, CloneMainSessionGoesAwayDuringFlush) {
+  // The consumer that creates the initial tracing session.
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer1 = CreateMockProducer();
+  producer1->Connect(svc.get(), "mock_producer1");
+  producer1->RegisterDataSource("ds_1");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(1024);  // Buf 0.
+  auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+  ds_cfg->set_name("ds_1");
+  ds_cfg->set_target_buffer(0);
+
+  consumer->EnableTracing(trace_config);
+  producer1->WaitForTracingSetup();
+  producer1->WaitForDataSourceSetup("ds_1");
+  producer1->WaitForDataSourceStart("ds_1");
+
+  std::unique_ptr<TraceWriter> writer1 = producer1->CreateTraceWriter("ds_1");
+
+  {
+    auto tp = writer1->NewTracePacket();
+    tp->set_for_testing()->set_str("buf1_beforeflush");
+  }
+  writer1->Flush();
+
+  std::unique_ptr<MockConsumer> clone_consumer = CreateMockConsumer();
+  clone_consumer->Connect(svc.get());
+
+  EXPECT_CALL(*clone_consumer, OnSessionCloned)
+      .Times(1)
+      .WillOnce(Invoke([](const Consumer::OnSessionClonedArgs& args) {
+        EXPECT_FALSE(args.success);
+        EXPECT_THAT(args.error, HasSubstr("Original session ended"));
+      }));
+  clone_consumer->CloneSession(1);
+
+  std::string producer1_flush_checkpoint_name = "producer1_flush_requested";
+  auto flush1_requested =
+      task_runner.CreateCheckpoint(producer1_flush_checkpoint_name);
+  FlushRequestID flush1_req_id;
+
+  // CloneSession() will issue a flush.
+  EXPECT_CALL(*producer1, Flush(_, _, _, _))
+      .WillOnce([&](FlushRequestID flush_id, const DataSourceInstanceID*,
+                    size_t, FlushFlags) {
+        flush1_req_id = flush_id;
+        flush1_requested();
+      });
+
+  task_runner.RunUntilCheckpoint(producer1_flush_checkpoint_name);
+
+  // The main session goes away.
+  consumer->DisableTracing();
+  producer1->WaitForDataSourceStop("ds_1");
+  consumer->WaitForTracingDisabled();
+  consumer.reset();
+
+  // producer1 replies to flush much later.
+  producer1->endpoint()->NotifyFlushComplete(flush1_req_id);
+  task_runner.RunUntilIdle();
+}
+
 TEST_F(TracingServiceImplTest, CloneTransferFlush) {
   // The consumer the creates the initial tracing session.
   std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
diff --git a/test/data/trace_with_negative_timestamp.sha256 b/test/data/trace_with_negative_timestamp.sha256
new file mode 100644
index 0000000..7777c8d
--- /dev/null
+++ b/test/data/trace_with_negative_timestamp.sha256
@@ -0,0 +1 @@
+79d809480f1bea625982e05ed726580a372656f03e7b7e29de4579e578ca13c4
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index a531d3a..d3dba5c 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -48,6 +48,7 @@
 from diff_tests.metrics.webview.tests import WebView
 from diff_tests.parser.android_fs.tests import AndroidFs
 from diff_tests.parser.android.tests import AndroidParser
+from diff_tests.parser.android.tests_android_input_event import AndroidInputEvent
 from diff_tests.parser.android.tests_bugreport import AndroidBugreport
 from diff_tests.parser.android.tests_games import AndroidGames
 from diff_tests.parser.android.tests_inputmethod_clients import InputMethodClients
@@ -225,6 +226,7 @@
       *ParsingTracedStats(index_path, 'parser/parsing',
                           'ParsingTracedStats').fetch(),
       *Zip(index_path, 'parser/zip', 'Zip').fetch(),
+      *AndroidInputEvent(index_path, 'parser/android', 'AndroidInputEvent').fetch(),
   ]
 
   metrics_tests = [
diff --git a/test/trace_processor/diff_tests/parser/android/input_event_trace.textproto b/test/trace_processor/diff_tests/parser/android/input_event_trace.textproto
new file mode 100644
index 0000000..02ecce4
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/input_event_trace.textproto
@@ -0,0 +1,1008 @@
+packet {
+  clock_snapshot {
+    primary_trace_clock: BUILTIN_CLOCK_BOOTTIME
+    clocks {
+      clock_id: 6
+      timestamp: 178674061789798
+    }
+    clocks {
+      clock_id: 2
+      timestamp: 1715375168889637820
+    }
+    clocks {
+      clock_id: 4
+      timestamp: 64176147060835
+    }
+    clocks {
+      clock_id: 1
+      timestamp: 1715375168892622806
+    }
+    clocks {
+      clock_id: 3
+      timestamp: 64176150045943
+    }
+    clocks {
+      clock_id: 5
+      timestamp: 64176150046147
+    }
+  }
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+}
+packet {
+  clock_snapshot {
+    primary_trace_clock: BUILTIN_CLOCK_BOOTTIME
+    clocks {
+      clock_id: 6
+      timestamp: 178674061806929
+    }
+    clocks {
+      clock_id: 2
+      timestamp: 1715375168889637820
+    }
+    clocks {
+      clock_id: 4
+      timestamp: 64176147060835
+    }
+    clocks {
+      clock_id: 1
+      timestamp: 1715375168892639855
+    }
+    clocks {
+      clock_id: 3
+      timestamp: 64176150062992
+    }
+    clocks {
+      clock_id: 5
+      timestamp: 64176150063155
+    }
+  }
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+}
+packet {
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+  synchronization_marker: "\202Gzv\262\215B\272\201\33432mW\240y"
+}
+packet {
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+  trace_config {
+    buffers {
+      size_kb: 1000000
+      fill_policy: RING_BUFFER
+    }
+    data_sources {
+      config {
+        name: "android.input.inputevent"
+        android_input_event_config {
+          mode: TRACE_MODE_TRACE_ALL
+        }
+      }
+    }
+    duration_ms: 10000
+    enable_extra_guardrails: false
+    statsd_metadata {
+    }
+    statsd_logging: STATSD_LOGGING_DISABLED
+    trace_uuid_msb: 6148766808500972866
+    trace_uuid_lsb: -1402094119056663186
+  }
+}
+packet {
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+  trace_uuid {
+    lsb: -1402094119056663186
+    msb: 6148766808500972866
+  }
+}
+packet {
+  system_info {
+    tracing_service_version: "Perfetto v44.0 (N/A)"
+    timezone_off_mins: 0
+    utsname {
+      sysname: "Linux"
+      version: "#1 SMP PREEMPT Thu Mar 28 12:45:51 UTC 2024"
+      machine: "aarch64"
+      release: "5.10.209-android13-4-02808-g9f408f561c85-ab11640454"
+    }
+    page_size: 4096
+    num_cpus: 8
+    android_build_fingerprint: "google/tangorpro/tangorpro:VanillaIceCream/MAIN/eng.prabir.20240508.183909:eng/dev-keys"
+    android_sdk_version: 35
+  }
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+}
+packet {
+  timestamp: 178674061798587
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+  service_event {
+    tracing_started: true
+  }
+}
+packet {
+  timestamp: 178674065835493
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+  service_event {
+    all_data_sources_started: true
+  }
+}
+packet {
+  timestamp: 178683982315600
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+  service_event {
+    all_data_sources_flushed: true
+  }
+}
+packet {
+  timestamp: 178683984185189
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+  service_event {
+    tracing_disabled: true
+  }
+}
+packet {
+  first_packet_on_sequence: true
+  android_input_event {
+    dispatcher_motion_event {
+      event_id: 104114844
+      event_time_nanos: 64179212500000
+      down_time_nanos: 64179212500000
+      source: 4098
+      action: 0
+      device_id: 4
+      display_id: 0
+      classification: 0
+      flags: 0
+      policy_flags: 1644167168
+      cursor_position_x: nan
+      cursor_position_y: nan
+      meta_state: 0
+      pointer {
+        pointer_id: 0
+        tool_type: 1
+        axis_value {
+          axis: 0
+          value: 580.000000
+        }
+        axis_value {
+          axis: 1
+          value: 798.000000
+        }
+        axis_value {
+          axis: 2
+          value: 1.003906
+        }
+        axis_value {
+          axis: 3
+          value: 0.033998
+        }
+        axis_value {
+          axis: 4
+          value: 92.000000
+        }
+        axis_value {
+          axis: 5
+          value: 82.000000
+        }
+        axis_value {
+          axis: 6
+          value: 92.000000
+        }
+        axis_value {
+          axis: 7
+          value: 82.000000
+        }
+        axis_value {
+          axis: 8
+          value: 0.983282
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+  previous_packet_dropped: true
+}
+packet {
+  android_input_event {
+    dispatcher_motion_event {
+      event_id: 1141228253
+      event_time_nanos: 64179212500000
+      down_time_nanos: 64179212500000
+      source: 4098
+      action: 4
+      device_id: 4
+      display_id: 0
+      classification: 0
+      flags: 0
+      policy_flags: 1644167168
+      cursor_position_x: nan
+      cursor_position_y: nan
+      meta_state: 0
+      pointer {
+        pointer_id: 0
+        tool_type: 1
+        axis_value {
+          axis: 0
+          value: 580.000000
+        }
+        axis_value {
+          axis: 1
+          value: 798.000000
+        }
+        axis_value {
+          axis: 2
+          value: 1.003906
+        }
+        axis_value {
+          axis: 3
+          value: 0.033998
+        }
+        axis_value {
+          axis: 4
+          value: 92.000000
+        }
+        axis_value {
+          axis: 5
+          value: 82.000000
+        }
+        axis_value {
+          axis: 6
+          value: 92.000000
+        }
+        axis_value {
+          axis: 7
+          value: 82.000000
+        }
+        axis_value {
+          axis: 8
+          value: 0.983282
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 1141228253
+      vsync_id: 182239
+      window_id: 105
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: -681.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.554132
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 104114844
+      vsync_id: 182239
+      window_id: 181
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.554157
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 104114844
+      vsync_id: 182239
+      window_id: 58
+      resolved_flags: 3
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1718.181641
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 600.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.554074
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 104114844
+      vsync_id: 182239
+      window_id: 76
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.554157
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 104114844
+      vsync_id: 182239
+      window_id: 68
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.554157
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 104114844
+      vsync_id: 0
+      window_id: 0
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.554157
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_motion_event {
+      event_id: 843076721
+      event_time_nanos: 64179262122000
+      down_time_nanos: 64179212500000
+      source: 4098
+      action: 2
+      device_id: 4
+      display_id: 0
+      classification: 1
+      flags: 0
+      policy_flags: 1644167168
+      cursor_position_x: nan
+      cursor_position_y: nan
+      meta_state: 0
+      pointer {
+        pointer_id: 0
+        tool_type: 1
+        axis_value {
+          axis: 0
+          value: 580.000000
+        }
+        axis_value {
+          axis: 1
+          value: 798.000000
+        }
+        axis_value {
+          axis: 2
+          value: 0.113281
+        }
+        axis_value {
+          axis: 3
+          value: 0.011333
+        }
+        axis_value {
+          axis: 4
+          value: 29.000000
+        }
+        axis_value {
+          axis: 5
+          value: 29.000000
+        }
+        axis_value {
+          axis: 6
+          value: 29.000000
+        }
+        axis_value {
+          axis: 7
+          value: 29.000000
+        }
+        axis_value {
+          axis: 8
+          value: 0.912335
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 843076721
+      vsync_id: 182239
+      window_id: 181
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483198
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 843076721
+      vsync_id: 182239
+      window_id: 58
+      resolved_flags: 3
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1718.181641
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 600.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483237
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 843076721
+      vsync_id: 182239
+      window_id: 76
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483198
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 843076721
+      vsync_id: 182239
+      window_id: 68
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483198
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 843076721
+      vsync_id: 0
+      window_id: 0
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483198
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_motion_event {
+      event_id: 744146837
+      event_time_nanos: 64179268985000
+      down_time_nanos: 64179212500000
+      source: 4098
+      action: 1
+      device_id: 4
+      display_id: 0
+      classification: 1
+      flags: 0
+      policy_flags: 1644167168
+      cursor_position_x: nan
+      cursor_position_y: nan
+      meta_state: 0
+      pointer {
+        pointer_id: 0
+        tool_type: 1
+        axis_value {
+          axis: 0
+          value: 580.000000
+        }
+        axis_value {
+          axis: 1
+          value: 798.000000
+        }
+        axis_value {
+          axis: 2
+          value: 0.113281
+        }
+        axis_value {
+          axis: 3
+          value: 0.011333
+        }
+        axis_value {
+          axis: 4
+          value: 29.000000
+        }
+        axis_value {
+          axis: 5
+          value: 29.000000
+        }
+        axis_value {
+          axis: 6
+          value: 29.000000
+        }
+        axis_value {
+          axis: 7
+          value: 29.000000
+        }
+        axis_value {
+          axis: 8
+          value: 0.912335
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 744146837
+      vsync_id: 182239
+      window_id: 181
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483198
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 744146837
+      vsync_id: 182239
+      window_id: 58
+      resolved_flags: 3
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1718.181641
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 600.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483237
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 744146837
+      vsync_id: 182239
+      window_id: 76
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483198
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 744146837
+      vsync_id: 182239
+      window_id: 68
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483198
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 744146837
+      vsync_id: 0
+      window_id: 0
+      resolved_flags: 0
+      dispatched_pointer {
+        pointer_id: 0
+        x_in_display: 1762.000000
+        y_in_display: 580.000000
+        axis_value_in_window {
+          axis: 0
+          value: 1762.000000
+        }
+        axis_value_in_window {
+          axis: 1
+          value: 580.000000
+        }
+        axis_value_in_window {
+          axis: 8
+          value: 2.483198
+        }
+      }
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_key_event {
+      event_id: 324105269
+      event_time_nanos: 64182660299000
+      down_time_nanos: 64182660299000
+      source: 257
+      action: 0
+      device_id: 2
+      display_id: -1
+      repeat_count: 0
+      flags: 8
+      policy_flags: 1644167168
+      key_code: 25
+      scan_code: 114
+      meta_state: 0
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 324105269
+      vsync_id: 182239
+      window_id: 181
+      resolved_flags: 8
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 324105269
+      vsync_id: 0
+      window_id: 0
+      resolved_flags: 8
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_key_event {
+      event_id: 60594531
+      event_time_nanos: 64182816340000
+      down_time_nanos: 64182660299000
+      source: 257
+      action: 1
+      device_id: 2
+      display_id: -1
+      repeat_count: 0
+      flags: 8
+      policy_flags: 1644167168
+      key_code: 25
+      scan_code: 114
+      meta_state: 0
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 60594531
+      vsync_id: 182239
+      window_id: 181
+      resolved_flags: 8
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  android_input_event {
+    dispatcher_window_dispatch_event {
+      event_id: 60594531
+      vsync_id: 0
+      window_id: 0
+      resolved_flags: 8
+    }
+  }
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1722
+}
+packet {
+  timestamp: 178683985660693
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+  service_event {
+    read_tracing_buffers_completed: true
+  }
+}
+packet {
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 1
+  trace_stats {
+    buffer_stats {
+      buffer_size: 1024000000
+      bytes_written: 4096
+      chunks_written: 1
+    }
+    producers_connected: 13
+    producers_seen: 16
+    data_sources_registered: 40
+    data_sources_seen: 13
+    tracing_sessions: 2
+    total_buffers: 4
+    chunks_discarded: 7
+    patches_discarded: 0
+    invalid_packets: 0
+    flushes_requested: 1
+    flushes_succeeded: 1
+    flushes_failed: 0
+    final_flush_outcome: FINAL_FLUSH_UNSPECIFIED
+  }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py b/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py
new file mode 100644
index 0000000..30ac616
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py
@@ -0,0 +1,272 @@
+#!/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 AndroidInputEvent(TestSuite):
+
+  def test_key_events_table(self):
+    return DiffTestBlueprint(
+      trace=Path('input_event_trace.textproto'),
+      query="""
+        INCLUDE PERFETTO MODULE android.input;
+        SELECT
+          event_id, ts
+        FROM
+          android_key_events;
+        """,
+      out=Csv("""
+        "event_id","ts"
+        324105269,64182660299000
+        60594531,64182816340000
+        """))
+
+  def test_key_events_args(self):
+    return DiffTestBlueprint(
+      trace=Path('input_event_trace.textproto'),
+      query="""
+        INCLUDE PERFETTO MODULE android.input;
+        SELECT
+          args.key, args.display_value
+        FROM
+          android_key_events AS e JOIN args ON e.arg_set_id = args.arg_set_id
+        WHERE e.event_id = 60594531
+        ORDER BY args.key;
+        """,
+      out=Csv("""
+        "key","display_value"
+        "action","1"
+        "device_id","2"
+        "display_id","-1"
+        "down_time_nanos","64182660299000"
+        "event_id","60594531"
+        "event_time_nanos","64182816340000"
+        "flags","8"
+        "key_code","25"
+        "meta_state","0"
+        "policy_flags","1644167168"
+        "repeat_count","0"
+        "scan_code","114"
+        "source","257"
+        """))
+
+  def test_motion_events_table(self):
+    return DiffTestBlueprint(
+      trace=Path('input_event_trace.textproto'),
+      query="""
+        INCLUDE PERFETTO MODULE android.input;
+        SELECT
+          event_id, ts
+        FROM
+          android_motion_events;
+        """,
+      out=Csv("""
+        "event_id","ts"
+        104114844,64179212500000
+        1141228253,64179212500000
+        843076721,64179262122000
+        744146837,64179268985000
+        """))
+
+  def test_motion_events_args(self):
+    return DiffTestBlueprint(
+      trace=Path('input_event_trace.textproto'),
+      query="""
+        INCLUDE PERFETTO MODULE android.input;
+        SELECT
+          args.key, args.display_value
+        FROM
+          android_motion_events AS e JOIN args ON e.arg_set_id = args.arg_set_id
+        WHERE e.event_id = 1141228253
+        ORDER BY args.key;
+        """,
+      out=Csv("""
+        "key","display_value"
+        "action","4"
+        "classification","0"
+        "cursor_position_x","[NULL]"
+        "cursor_position_y","[NULL]"
+        "device_id","4"
+        "display_id","0"
+        "down_time_nanos","64179212500000"
+        "event_id","1141228253"
+        "event_time_nanos","64179212500000"
+        "flags","0"
+        "meta_state","0"
+        "pointer[0].axis_value[0].axis","0"
+        "pointer[0].axis_value[0].value","580.0"
+        "pointer[0].axis_value[1].axis","1"
+        "pointer[0].axis_value[1].value","798.0"
+        "pointer[0].axis_value[2].axis","2"
+        "pointer[0].axis_value[2].value","1.00390601158142"
+        "pointer[0].axis_value[3].axis","3"
+        "pointer[0].axis_value[3].value","0.0339980013668537"
+        "pointer[0].axis_value[4].axis","4"
+        "pointer[0].axis_value[4].value","92.0"
+        "pointer[0].axis_value[5].axis","5"
+        "pointer[0].axis_value[5].value","82.0"
+        "pointer[0].axis_value[6].axis","6"
+        "pointer[0].axis_value[6].value","92.0"
+        "pointer[0].axis_value[7].axis","7"
+        "pointer[0].axis_value[7].value","82.0"
+        "pointer[0].axis_value[8].axis","8"
+        "pointer[0].axis_value[8].value","0.983282029628754"
+        "pointer[0].pointer_id","0"
+        "pointer[0].tool_type","1"
+        "policy_flags","1644167168"
+        "source","4098"
+        """))
+
+  def test_dispatch_table(self):
+    return DiffTestBlueprint(
+      trace=Path('input_event_trace.textproto'),
+      query="""
+        INCLUDE PERFETTO MODULE android.input;
+        SELECT
+          id, event_id, vsync_id, window_id
+        FROM
+          android_input_event_dispatch;
+        """,
+      out=Csv("""
+        "id","event_id","vsync_id","window_id"
+        0,1141228253,182239,105
+        1,104114844,182239,181
+        2,104114844,182239,58
+        3,104114844,182239,76
+        4,104114844,182239,68
+        5,104114844,0,0
+        6,843076721,182239,181
+        7,843076721,182239,58
+        8,843076721,182239,76
+        9,843076721,182239,68
+        10,843076721,0,0
+        11,744146837,182239,181
+        12,744146837,182239,58
+        13,744146837,182239,76
+        14,744146837,182239,68
+        15,744146837,0,0
+        16,324105269,182239,181
+        17,324105269,0,0
+        18,60594531,182239,181
+        19,60594531,0,0
+        """))
+
+  def test_motion_dispatch_args(self):
+    return DiffTestBlueprint(
+      trace=Path('input_event_trace.textproto'),
+      query="""
+        INCLUDE PERFETTO MODULE android.input;
+        SELECT
+          d.id, args.key, args.display_value
+        FROM
+          android_input_event_dispatch AS d JOIN args ON d.arg_set_id = args.arg_set_id
+        WHERE d.event_id = 104114844
+        ORDER BY d.id, args.key;
+        """,
+      out=Csv("""
+        "id","key","display_value"
+        1,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+        1,"dispatched_pointer[0].axis_value_in_window[0].value","1762.0"
+        1,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+        1,"dispatched_pointer[0].axis_value_in_window[1].value","580.0"
+        1,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+        1,"dispatched_pointer[0].axis_value_in_window[2].value","2.5541570186615"
+        1,"dispatched_pointer[0].pointer_id","0"
+        1,"dispatched_pointer[0].x_in_display","1762.0"
+        1,"dispatched_pointer[0].y_in_display","580.0"
+        1,"event_id","104114844"
+        1,"resolved_flags","0"
+        1,"vsync_id","182239"
+        1,"window_id","181"
+        2,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+        2,"dispatched_pointer[0].axis_value_in_window[0].value","1718.181640625"
+        2,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+        2,"dispatched_pointer[0].axis_value_in_window[1].value","600.0"
+        2,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+        2,"dispatched_pointer[0].axis_value_in_window[2].value","2.55407404899597"
+        2,"dispatched_pointer[0].pointer_id","0"
+        2,"dispatched_pointer[0].x_in_display","1762.0"
+        2,"dispatched_pointer[0].y_in_display","580.0"
+        2,"event_id","104114844"
+        2,"resolved_flags","3"
+        2,"vsync_id","182239"
+        2,"window_id","58"
+        3,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+        3,"dispatched_pointer[0].axis_value_in_window[0].value","1762.0"
+        3,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+        3,"dispatched_pointer[0].axis_value_in_window[1].value","580.0"
+        3,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+        3,"dispatched_pointer[0].axis_value_in_window[2].value","2.5541570186615"
+        3,"dispatched_pointer[0].pointer_id","0"
+        3,"dispatched_pointer[0].x_in_display","1762.0"
+        3,"dispatched_pointer[0].y_in_display","580.0"
+        3,"event_id","104114844"
+        3,"resolved_flags","0"
+        3,"vsync_id","182239"
+        3,"window_id","76"
+        4,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+        4,"dispatched_pointer[0].axis_value_in_window[0].value","1762.0"
+        4,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+        4,"dispatched_pointer[0].axis_value_in_window[1].value","580.0"
+        4,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+        4,"dispatched_pointer[0].axis_value_in_window[2].value","2.5541570186615"
+        4,"dispatched_pointer[0].pointer_id","0"
+        4,"dispatched_pointer[0].x_in_display","1762.0"
+        4,"dispatched_pointer[0].y_in_display","580.0"
+        4,"event_id","104114844"
+        4,"resolved_flags","0"
+        4,"vsync_id","182239"
+        4,"window_id","68"
+        5,"dispatched_pointer[0].axis_value_in_window[0].axis","0"
+        5,"dispatched_pointer[0].axis_value_in_window[0].value","1762.0"
+        5,"dispatched_pointer[0].axis_value_in_window[1].axis","1"
+        5,"dispatched_pointer[0].axis_value_in_window[1].value","580.0"
+        5,"dispatched_pointer[0].axis_value_in_window[2].axis","8"
+        5,"dispatched_pointer[0].axis_value_in_window[2].value","2.5541570186615"
+        5,"dispatched_pointer[0].pointer_id","0"
+        5,"dispatched_pointer[0].x_in_display","1762.0"
+        5,"dispatched_pointer[0].y_in_display","580.0"
+        5,"event_id","104114844"
+        5,"resolved_flags","0"
+        5,"vsync_id","0"
+        5,"window_id","0"
+        """))
+
+  def test_key_dispatch_args(self):
+    return DiffTestBlueprint(
+      trace=Path('input_event_trace.textproto'),
+      query="""
+        INCLUDE PERFETTO MODULE android.input;
+        SELECT
+          d.id, args.key, args.display_value
+        FROM
+          android_input_event_dispatch AS d JOIN args ON d.arg_set_id = args.arg_set_id
+        WHERE d.event_id = 324105269
+        ORDER BY d.id, args.key;
+        """,
+      out=Csv("""
+        "id","key","display_value"
+        16,"event_id","324105269"
+        16,"resolved_flags","8"
+        16,"vsync_id","182239"
+        16,"window_id","181"
+        17,"event_id","324105269"
+        17,"resolved_flags","8"
+        17,"vsync_id","0"
+        17,"window_id","0"
+        """))
diff --git a/ui/src/chrome_extension/index.ts b/ui/src/chrome_extension/index.ts
index 73bd10a..df6c033 100644
--- a/ui/src/chrome_extension/index.ts
+++ b/ui/src/chrome_extension/index.ts
@@ -21,7 +21,7 @@
 
 // Listen for messages from the perfetto ui.
 // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
-if (window.chrome) {
+if (globalThis.chrome) {
   chrome.runtime.onConnectExternal.addListener((port) => {
     chromeTraceController = new ChromeTracingController(port);
     port.onMessage.addListener(onUIMessage);
diff --git a/ui/src/chrome_extension/manifest.json b/ui/src/chrome_extension/manifest.json
index 0a26e6a..d16e143 100644
--- a/ui/src/chrome_extension/manifest.json
+++ b/ui/src/chrome_extension/manifest.json
@@ -2,9 +2,9 @@
   "name": "Perfetto UI",
   "key":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhm3X7qutsrskke84ltokTObnFJakd/d0XFQ6Ox2wQueHTGJM5GUNPTY/x8bdreNtGnfzvt/Sd0vABbR0wsS6lz5yY+g6ksMXJnigFe9N7uz8E3KojDrl3xYjIe+mkiJo8yxxzPydgb7GjQ6jmsX3g+yjj67kXzm9rZFkmoZ5WmqwBZlguPYVRN/W8CIIqBZkC3Qmq6uSG7b/g93YbwqmTmGiL2sAzgvXtqvDOD6503abtQkRC795E4VjJd+ffyeRH38fAEz5ZIrA6GJsfmov1TZTIu1NTwqylSpBYl5as7C6gpmuxDV4SvHvGT2hMQuIufDhZhErjI3B7bcX+XLe1wIDAQAB",
   "description": "Enables the Perfetto trace viewer (https://ui.perfetto.dev) to record Chrome browser traces.",
-  "version": "0.0.0.18",
-  "manifest_version": 2,
-  "minimum_chrome_version": "81.0.4022.0",
+  "version": "0.0.0.19",
+  "manifest_version": 3,
+  "minimum_chrome_version": "116.0.0.0",
   "permissions": [
     "declarativeContent",
     "debugger"
@@ -13,10 +13,7 @@
     "128": "logo-128.png"
   },
   "background": {
-    "scripts": [
-      "chrome_extension_bundle.js"
-    ],
-    "persistent": false
+    "service_worker": "chrome_extension_bundle.js"
   },
   "externally_connectable": {
     // Only allow Perfetto UI hosts to connect to the extension. Do not add
diff --git a/ui/src/common/recordingV2/recording_utils.ts b/ui/src/common/recordingV2/recording_utils.ts
index c698dd5..152ad96 100644
--- a/ui/src/common/recordingV2/recording_utils.ts
+++ b/ui/src/common/recordingV2/recording_utils.ts
@@ -44,6 +44,12 @@
 export function isLinux(userAgent: string) {
   return userAgent.toLowerCase().includes(' linux ');
 }
+// Sample user agent for Chrome on Windows:
+// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
+// like Gecko) Chrome/125.0.0.0 Safari/537.36
+export function isWindows(userAgent: string) {
+  return userAgent.toLowerCase().includes('windows ');
+}
 
 // Sample user agent for Chrome on Chrome OS:
 // "Mozilla/5.0 (X11; CrOS x86_64 14816.99.0) AppleWebKit/537.36
diff --git a/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts b/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
index 6037a31..f68d2f4 100644
--- a/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
+++ b/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
@@ -89,6 +89,6 @@
 
 // We only instantiate the factory if Perfetto UI is open in the Chrome browser.
 // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
-if (window.chrome && chrome.runtime) {
+if (globalThis.chrome && chrome.runtime) {
   targetFactoryRegistry.register(new ChromeTargetFactory());
 }
diff --git a/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts b/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts
index 1e5ed68..24eac91 100644
--- a/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts
+++ b/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts
@@ -18,7 +18,7 @@
   RecordingTargetV2,
   TargetFactory,
 } from '../recording_interfaces_v2';
-import {isLinux, isMacOs} from '../recording_utils';
+import {isLinux, isMacOs, isWindows} from '../recording_utils';
 import {targetFactoryRegistry} from '../target_factory_registry';
 import {HostOsTarget} from '../targets/host_os_target';
 
@@ -79,7 +79,11 @@
   }
 }
 
-// We instantiate the host target factory only on Mac and Linux.
-if (isMacOs(navigator.userAgent) || isLinux(navigator.userAgent)) {
+// We instantiate the host target factory only on Mac, Linux, and Windows.
+if (
+  isMacOs(navigator.userAgent) ||
+  isLinux(navigator.userAgent) ||
+  isWindows(navigator.userAgent)
+) {
   targetFactoryRegistry.register(new HostOsTargetFactory());
 }
diff --git a/ui/src/common/recordingV2/targets/host_os_target.ts b/ui/src/common/recordingV2/targets/host_os_target.ts
index 7b32fcc..06962f8 100644
--- a/ui/src/common/recordingV2/targets/host_os_target.ts
+++ b/ui/src/common/recordingV2/targets/host_os_target.ts
@@ -26,12 +26,13 @@
 import {
   isLinux,
   isMacOs,
+  isWindows,
   WEBSOCKET_CLOSED_ABNORMALLY_CODE,
 } from '../recording_utils';
 import {TracedTracingSession} from '../traced_tracing_session';
 
 export class HostOsTarget implements RecordingTargetV2 {
-  private readonly targetType: 'LINUX' | 'MACOS';
+  private readonly targetType: 'LINUX' | 'MACOS' | 'WINDOWS';
   private readonly name: string;
   private websocket: WebSocket;
   private streams = new Set<HostOsByteStream>();
@@ -49,6 +50,9 @@
     } else if (isLinux(navigator.userAgent)) {
       this.name = 'Linux';
       this.targetType = 'LINUX';
+    } else if (isWindows(navigator.userAgent)) {
+      this.name = 'Windows Desktop';
+      this.targetType = 'WINDOWS';
     } else {
       throw new RecordingError(
         'Host OS target created on an unsupported operating system.',
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 1398581..ba87a8b 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -24,6 +24,7 @@
   AdbRecordingTarget,
   isAdbTarget,
   isChromeTarget,
+  isWindowsTarget,
   RecordingTarget,
 } from '../common/state';
 import {globals} from '../frontend/globals';
@@ -398,7 +399,7 @@
     const controllerPromise = new Promise<RpcConsumerPort>(
       async (resolve, _) => {
         let controller: RpcConsumerPort | undefined = undefined;
-        if (isChromeTarget(target)) {
+        if (isChromeTarget(target) || isWindowsTarget(target)) {
           controller = new ChromeExtensionConsumerPort(
             this.extensionPort,
             this,
diff --git a/ui/src/core_plugins/async_slices/async_slice_track.ts b/ui/src/core_plugins/async_slices/async_slice_track.ts
index 275c418..b191d04 100644
--- a/ui/src/core_plugins/async_slices/async_slice_track.ts
+++ b/ui/src/core_plugins/async_slices/async_slice_track.ts
@@ -32,15 +32,15 @@
 
   getSqlSource(): string {
     return `
-    select
-      ts,
-      dur,
-      layout_depth as depth,
-      ifnull(name, '[null]') as name,
-      id,
-      thread_dur as threadDur
-    from experimental_slice_layout
-    where filter_track_ids = '${this.trackIds.join(',')}'
+      select
+        ts,
+        dur,
+        layout_depth as depth,
+        ifnull(name, '[null]') as name,
+        id,
+        thread_dur as threadDur
+      from experimental_slice_layout
+      where filter_track_ids = '${this.trackIds.join(',')}'
     `;
   }
 
diff --git a/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts b/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
index a8273b4..c57a63e 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
@@ -36,9 +36,16 @@
   }
 
   getSqlSource(): string {
-    return `select s2.ts as ts, s2.dur as dur, s2.id as id, 0 as depth, s1.full_name as name
-from chrome_tasks_delaying_input_processing s1
-join slice s2 on s2.id=s1.slice_id`;
+    return `
+      select
+        s2.ts as ts,
+        s2.dur as dur,
+        s2.id as id,
+        0 as depth,
+        s1.full_name as name
+      from chrome_tasks_delaying_input_processing s1
+      join slice s2 on s2.id=s1.slice_id
+    `;
   }
 }
 export type GetTrackGroupUuidFn = (utid: number, upid: number | null) => string;
@@ -55,10 +62,12 @@
   }
 
   const queryResult = await engine.query(`
-    select utid, upid
+    select
+      utid,
+      upid
     from thread
     where name='CrBrowserMain'
-    `);
+  `);
 
   const it = queryResult.iter({
     utid: NUM,
diff --git a/ui/src/core_plugins/counter/trace_processor_counter_track.ts b/ui/src/core_plugins/counter/trace_processor_counter_track.ts
index a80ac4b..b47a03d 100644
--- a/ui/src/core_plugins/counter/trace_processor_counter_track.ts
+++ b/ui/src/core_plugins/counter/trace_processor_counter_track.ts
@@ -37,7 +37,13 @@
   }
 
   getSqlSource() {
-    return `select ts, value from ${this.rootTable} where track_id = ${this.trackId}`;
+    return `
+      select
+        ts,
+        value
+      from ${this.rootTable}
+      where track_id = ${this.trackId}
+    `;
   }
 
   onMouseClick({x}: {x: number}): boolean {
diff --git a/ui/src/core_plugins/heap_profile/heap_profile_track.ts b/ui/src/core_plugins/heap_profile/heap_profile_track.ts
index 33e28f8..264a216 100644
--- a/ui/src/core_plugins/heap_profile/heap_profile_track.ts
+++ b/ui/src/core_plugins/heap_profile/heap_profile_track.ts
@@ -52,10 +52,11 @@
   }
 
   getSqlSource(): string {
-    return `select
-      *,
-      0 AS dur,
-      0 AS depth
+    return `
+      select
+        *,
+        0 AS dur,
+        0 AS depth
       from (
         select distinct
           id,
@@ -70,7 +71,8 @@
           'graph' AS type
         from heap_graph_object
         where upid = ${this.upid}
-      )`;
+      )
+    `;
   }
 
   getRowSpec(): HeapProfileRow {
diff --git a/ui/src/core_plugins/sched/active_cpu_count.ts b/ui/src/core_plugins/sched/active_cpu_count.ts
index 2ab49bc..f3a4eee 100644
--- a/ui/src/core_plugins/sched/active_cpu_count.ts
+++ b/ui/src/core_plugins/sched/active_cpu_count.ts
@@ -96,10 +96,10 @@
             this.config!.cpuType,
           )})`;
     return `
-    select
-      ts,
-      active_cpu_count as value
-    from ${sourceTable}
+      select
+        ts,
+        active_cpu_count as value
+      from ${sourceTable}
     `;
   }
 }
diff --git a/ui/src/core_plugins/sched/runnable_thread_count.ts b/ui/src/core_plugins/sched/runnable_thread_count.ts
index f5ab7ca..894824e 100644
--- a/ui/src/core_plugins/sched/runnable_thread_count.ts
+++ b/ui/src/core_plugins/sched/runnable_thread_count.ts
@@ -70,10 +70,10 @@
 
   getSqlSource() {
     return `
-    select
-      ts,
-      runnable_thread_count as value
-    from sched_runnable_thread_count
+      select
+        ts,
+        runnable_thread_count as value
+      from sched_runnable_thread_count
     `;
   }
 }
diff --git a/ui/src/core_plugins/thread_slice/thread_slice_track.ts b/ui/src/core_plugins/thread_slice/thread_slice_track.ts
index 3e50028..90e9f90 100644
--- a/ui/src/core_plugins/thread_slice/thread_slice_track.ts
+++ b/ui/src/core_plugins/thread_slice/thread_slice_track.ts
@@ -60,15 +60,17 @@
   }
 
   getSqlSource(): string {
-    return `select
-      ts,
-      dur,
-      id,
-      depth,
-      ifnull(name, '') as name,
-      thread_dur as threadDur
-    from ${this.tableName}
-    where track_id = ${this.trackId}`;
+    return `
+      select
+        ts,
+        dur,
+        id,
+        depth,
+        ifnull(name, '') as name,
+        thread_dur as threadDur
+      from ${this.tableName}
+      where track_id = ${this.trackId}
+    `;
   }
 
   // Converts a SQL result row to an "Impl" Slice.
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index 49cc7b3..cd5aef3 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -436,6 +436,14 @@
     '.note',
     `To trace Chrome from the Perfetto UI, you need to install our `,
     m('a', {href: extensionURL, target: '_blank'}, 'Chrome extension'),
+    ' and then reload this page. ',
+  );
+
+  const msgWinEtw = m(
+    '.note',
+    `To trace with Etw on Windows from the Perfetto UI, you to run chrome with`,
+    `administrator permission and you need to install our `,
+    m('a', {href: extensionURL, target: '_blank'}, 'Chrome extension'),
     ' and then reload this page.',
   );
 
@@ -488,6 +496,9 @@
     case 'CrOS':
       if (!globals.state.extensionInstalled) notes.push(msgChrome);
       break;
+    case 'Win':
+      if (!globals.state.extensionInstalled) notes.push(msgWinEtw);
+      break;
     default:
   }
   if (globals.state.recordConfig.mode === 'LONG_TRACE') {
@@ -575,7 +586,10 @@
     ) {
       buttons.push(start);
     }
-  } else if (isChromeTarget(target) && state.extensionInstalled) {
+  } else if (
+    (isWindowsTarget(target) || isChromeTarget(target)) &&
+    state.extensionInstalled
+  ) {
     buttons.push(start);
   }
   return m('.button', buttons);
@@ -605,7 +619,11 @@
   autosaveConfigStore.save(globals.state.recordConfig);
 
   const target = globals.state.recordingTarget;
-  if (isAndroidTarget(target) || isChromeTarget(target)) {
+  if (
+    isAndroidTarget(target) ||
+    isChromeTarget(target) ||
+    isWindowsTarget(target)
+  ) {
     globals.logging.logEvent('Record Trace', `Record trace (${target.os})`);
     globals.dispatch(Actions.startRecording({}));
   }
diff --git a/ui/src/frontend/record_page_v2.ts b/ui/src/frontend/record_page_v2.ts
index dc1d015..797024f 100644
--- a/ui/src/frontend/record_page_v2.ts
+++ b/ui/src/frontend/record_page_v2.ts
@@ -318,7 +318,7 @@
 
 function RecordingSnippet(targetInfo: TargetInfo) {
   // We don't need commands to start tracing on chrome
-  if (isChromeTargetInfo(targetInfo)) {
+  if (isChromeTargetInfo(targetInfo) || targetInfo.targetType === 'WINDOWS') {
     if (controller.getState() > RecordingState.AUTH_P2) {
       // If the UI has started tracing, don't display a message guiding the user
       // to start recording.
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index 79af0e1..3f8265b 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -100,7 +100,7 @@
               when t.name glob '*rmnet*' then 'modem'
               else 'unknown'
           end as dev_type,
-          lower(substr(t.name, instr(t.name, ' ') + 1, 1)) || 'x' as dir,
+          s.name as pkg,
           sum(EXTRACT_ARG(arg_set_id, 'packet_length')) AS value
       from slice s join track t on s.track_id = t.id
       where (t.name glob '*Received' or t.name glob '*Transmitted')
@@ -111,14 +111,14 @@
       select
           ts,
           dev_type,
-          dir,
+          pkg,
           value
       from base
       union all
       select
           ts + 5000000000 as ts,
           dev_type,
-          dir,
+          pkg,
           0 as value
       from base
   ),
@@ -126,7 +126,7 @@
       select
           ts,
           dev_type,
-          dir,
+          pkg,
           sum(value) as value
       from zeroes
       group by 1, 2, 3
@@ -1277,69 +1277,25 @@
     if (features.has('net.wifi')) {
       this.addCounterTrack(
         ctx,
-        'Wifi bytes',
+        'Wifi total',
         `select ts, sum(value) as value from network_summary where dev_type = 'wifi' group by 1`,
         groupName,
         {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
       );
-      this.addCounterTrack(
-        ctx,
-        'Wifi TX bytes',
-        `select ts, value from network_summary where dev_type = 'wifi' and dir = 'tx'`,
-        groupName,
-        {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
+      const result = await e.query(
+        `select pkg, sum(value) from network_summary where dev_type='wifi' group by 1 order by 2 desc limit 10`,
       );
-      this.addCounterTrack(
-        ctx,
-        'Wifi RX bytes',
-        `select ts, value from network_summary where dev_type = 'wifi' and dir = 'rx'`,
-        groupName,
-        {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
-      );
+      const it = result.iter({pkg: 'str'});
+      for (; it.valid(); it.next()) {
+        this.addCounterTrack(
+          ctx,
+          `Top wifi: ${it.pkg}`,
+          `select ts, value from network_summary where dev_type = 'wifi' and pkg = '${it.pkg}'`,
+          groupName,
+          {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
+        );
+      }
     }
-    if (features.has('net.modem')) {
-      this.addCounterTrack(
-        ctx,
-        'Modem bytes',
-        `select ts, sum(value) as value from network_summary where dev_type = 'modem' group by 1`,
-        groupName,
-        {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
-      );
-      this.addCounterTrack(
-        ctx,
-        'Modem TX bytes',
-        `select ts, value from network_summary where dev_type = 'modem' and dir = 'tx'`,
-        groupName,
-        {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
-      );
-      this.addCounterTrack(
-        ctx,
-        'Modem RX bytes',
-        `select ts, value from network_summary where dev_type = 'modem' and dir = 'rx'`,
-        groupName,
-        {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
-      );
-    }
-    this.addBatteryStatsState(
-      ctx,
-      'Cellular interface',
-      'battery_stats.mobile_radio',
-      groupName,
-      features,
-    );
-    this.addSliceTrack(
-      ctx,
-      'Cellular connection',
-      `select ts, dur, name from radio_transport`,
-      groupName,
-    );
-    this.addBatteryStatsState(
-      ctx,
-      'Cellular strength',
-      'battery_stats.phone_signal_strength',
-      groupName,
-      features,
-    );
     this.addBatteryStatsState(
       ctx,
       'Wifi interface',
@@ -1361,6 +1317,48 @@
       groupName,
       features,
     );
+    if (features.has('net.modem')) {
+      this.addCounterTrack(
+        ctx,
+        'Modem total',
+        `select ts, sum(value) as value from network_summary where dev_type = 'modem' group by 1`,
+        groupName,
+        {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
+      );
+      const result = await e.query(
+        `select pkg, sum(value) from network_summary where dev_type='modem' group by 1 order by 2 desc limit 10`,
+      );
+      const it = result.iter({pkg: 'str'});
+      for (; it.valid(); it.next()) {
+        this.addCounterTrack(
+          ctx,
+          `Top modem: ${it.pkg}`,
+          `select ts, value from network_summary where dev_type = 'modem' and pkg = '${it.pkg}'`,
+          groupName,
+          {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'},
+        );
+      }
+    }
+    this.addBatteryStatsState(
+      ctx,
+      'Cellular interface',
+      'battery_stats.mobile_radio',
+      groupName,
+      features,
+    );
+    this.addSliceTrack(
+      ctx,
+      'Cellular connection',
+      `select ts, dur, name from radio_transport`,
+      groupName,
+    );
+    this.addBatteryStatsState(
+      ctx,
+      'Cellular strength',
+      'battery_stats.phone_signal_strength',
+      groupName,
+      features,
+    );
   }
 
   async addModemDetail(