tp: Add pixel.touch stdlib module for touch events

Introduces the pixel.touch module to parse Pixel-specific touch events
(IRQ top-half/bottom-half and Twoshay processing timestamps) from the
trace.

Bug: 454761692
diff --git a/Android.bp b/Android.bp
index 01c98aa..d42dc41 100644
--- a/Android.bp
+++ b/Android.bp
@@ -18700,6 +18700,7 @@
     name: "perfetto_src_trace_processor_perfetto_sql_stdlib_pixel_pixel",
     srcs: [
         "src/trace_processor/perfetto_sql/stdlib/pixel/camera.sql",
+        "src/trace_processor/perfetto_sql/stdlib/pixel/touch.sql",
     ],
 }
 
diff --git a/BUILD b/BUILD
index e509f73..e8c9122 100644
--- a/BUILD
+++ b/BUILD
@@ -4049,6 +4049,7 @@
     name = "src_trace_processor_perfetto_sql_stdlib_pixel_pixel",
     srcs = [
         "src/trace_processor/perfetto_sql/stdlib/pixel/camera.sql",
+        "src/trace_processor/perfetto_sql/stdlib/pixel/touch.sql",
     ],
 )
 
diff --git a/src/trace_processor/perfetto_sql/stdlib/pixel/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/pixel/BUILD.gn
index cd19df3..4f872b0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/pixel/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/pixel/BUILD.gn
@@ -15,5 +15,8 @@
 import("../../../../../gn/perfetto_sql.gni")
 
 perfetto_sql_source_set("pixel") {
-  sources = [ "camera.sql" ]
+  sources = [
+    "camera.sql",
+    "touch.sql",
+  ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/pixel/touch.sql b/src/trace_processor/perfetto_sql/stdlib/pixel/touch.sql
new file mode 100644
index 0000000..240d13f
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/pixel/touch.sql
@@ -0,0 +1,155 @@
+-- Copyright (C) 2026 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 PERFETTO MODULE intervals.intersect;
+
+CREATE PERFETTO TABLE _pixel_touch_twoshay_events AS
+WITH
+  base AS (
+    SELECT
+      s.id AS twoshay_slice_id,
+      s.ts AS twoshay_ts,
+      s.dur AS twoshay_dur,
+      cast_int!(STR_SPLIT(STR_SPLIT(s.name, 'IN_TS=', 1), '.', 0)) AS in_ts
+    FROM slice AS s
+    WHERE
+      s.name GLOB 'algo->processFrame:*'
+  )
+SELECT
+  twoshay_slice_id,
+  twoshay_ts,
+  twoshay_dur,
+  in_ts,
+  cast_int!(IFNULL(
+      (
+        SELECT c.value
+        FROM counter AS c
+        WHERE
+          c.track_id
+          = (
+            SELECT id FROM track WHERE name = 'Resample latency offset' LIMIT 1
+          )
+          AND c.ts >= twoshay_ts
+          AND c.ts <= twoshay_ts + twoshay_dur
+        LIMIT 1
+      ),
+      0
+    )) AS resample_latency_offset
+FROM base;
+
+CREATE PERFETTO TABLE _pixel_touch_bottom_half_events AS
+WITH
+  bh_base AS (
+    SELECT
+      id AS bh_slice_id,
+      ts AS bh_start_ts,
+      dur AS bh_dur,
+      track_id,
+      cast_int!(STR_SPLIT(STR_SPLIT(name, 'IRQ_IDX=', 1), '.', 0)) AS irq_idx
+    FROM slice
+    WHERE
+      name GLOB 'gti_irq_thread_fn: IRQ_IDX=*'
+  ),
+  bh_with_in_ts AS (
+    SELECT
+      b.*,
+      (
+        SELECT cast_int!(STR_SPLIT(STR_SPLIT(s.name, 'IN_TS=', 1), '.', 0))
+        FROM slice AS s
+        WHERE
+          s.parent_id = b.bh_slice_id
+          AND s.name GLOB 'goog_offload_populate_frame:*'
+        LIMIT 1
+      ) AS in_ts
+    FROM bh_base AS b
+  )
+SELECT * FROM bh_with_in_ts;
+
+CREATE PERFETTO TABLE _pixel_touch_top_half_events AS
+WITH
+  irq_counters AS (
+    SELECT id, ts, 0 AS dur, value, track_id
+    FROM counter
+    WHERE
+      track_id = (SELECT id FROM track WHERE name = 'gti_th_irq_index' LIMIT 1)
+  ),
+  irq_slices AS (
+    SELECT id, ts, dur, track_id FROM slice WHERE name GLOB 'IRQ (*)'
+  )
+SELECT
+  s.id AS th_slice_id,
+  s.ts AS th_start_ts,
+  s.dur AS th_dur,
+  s.track_id AS th_track_id,
+  c.value AS irq_idx
+FROM _interval_intersect!((irq_slices, irq_counters), ()) AS ii
+JOIN irq_slices AS s
+  ON s.id = ii.id_0
+JOIN irq_counters AS c
+  ON c.id = ii.id_1;
+
+-- Pixel-specific touch events, including top and bottom half IRQ, and Twoshay touch processing.
+CREATE PERFETTO TABLE pixel_touch_events(
+  -- Hardware input event timestamp parsed from Twoshay driver logs.
+  in_ts LONG,
+  -- Start timestamp of the top-half touch IRQ.
+  ts_pixel_touch_th TIMESTAMP,
+  -- Duration of the top-half touch IRQ.
+  dur_pixel_touch_th DURATION,
+  -- Slice ID of the top-half touch IRQ.
+  id_pixel_touch_th LONG,
+  -- Track ID of the top-half touch IRQ.
+  track_pixel_touch_th JOINID(track.id),
+  -- Start timestamp of the bottom-half touch IRQ thread.
+  ts_pixel_touch_bh TIMESTAMP,
+  -- Duration of the bottom-half touch IRQ thread.
+  dur_pixel_touch_bh DURATION,
+  -- Slice ID of the bottom-half touch IRQ thread.
+  id_pixel_touch_bh LONG,
+  -- Track ID of the bottom-half touch IRQ thread.
+  track_pixel_touch_bh JOINID(track.id),
+  -- Start timestamp of the Twoshay processing frame.
+  ts_pixel_touch TIMESTAMP,
+  -- Duration of the Twoshay processing frame.
+  dur_pixel_touch DURATION,
+  -- Slice ID of the Twoshay processing frame.
+  id_pixel_touch LONG,
+  -- Track ID of the Twoshay processing frame.
+  track_pixel_touch JOINID(track.id),
+  -- Resample latency offset (in nanoseconds).
+  resample_latency_offset LONG
+)
+AS
+SELECT
+  t.in_ts AS in_ts,
+  th.th_start_ts AS ts_pixel_touch_th,
+  th.th_dur AS dur_pixel_touch_th,
+  th.th_slice_id AS id_pixel_touch_th,
+  th.th_track_id AS track_pixel_touch_th,
+  bh.bh_start_ts AS ts_pixel_touch_bh,
+  bh.bh_dur AS dur_pixel_touch_bh,
+  bh.bh_slice_id AS id_pixel_touch_bh,
+  bh.track_id AS track_pixel_touch_bh,
+  t.twoshay_ts AS ts_pixel_touch,
+  t.twoshay_dur AS dur_pixel_touch,
+  t.twoshay_slice_id AS id_pixel_touch,
+  s.track_id AS track_pixel_touch,
+  t.resample_latency_offset AS resample_latency_offset
+FROM _pixel_touch_twoshay_events AS t
+LEFT JOIN _pixel_touch_bottom_half_events AS bh
+  ON bh.in_ts = t.in_ts
+LEFT JOIN _pixel_touch_top_half_events AS th
+  ON th.irq_idx = bh.irq_idx
+LEFT JOIN slice AS s
+  ON s.id = t.twoshay_slice_id;
diff --git a/test/trace_processor/diff_tests/stdlib/pixel/tests.py b/test/trace_processor/diff_tests/stdlib/pixel/tests.py
index 1cbc02b..04ccb8b 100644
--- a/test/trace_processor/diff_tests/stdlib/pixel/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/pixel/tests.py
@@ -55,3 +55,364 @@
         "ts","node","port_group","frame_number","cam_id","dur"
         2200,"filter","output",123,1,500
         """))
+
+  def test_pixel_touch_events(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          system_info {
+            utsname {
+              sysname: "Linux"
+              machine: "x86_64"
+            }
+          }
+        }
+        packet {
+          process_tree {
+            processes {
+              pid: 2
+              ppid: 0
+              cmdline: "kthreadd"
+            }
+            processes {
+              pid: 30724
+              ppid: 2
+              cmdline: "irq/764-touch_dev"
+            }
+            processes {
+              pid: 9876
+              ppid: 1
+              cmdline: "twoshay"
+            }
+            threads {
+              tid: 30724
+              tgid: 30724
+              name: "irq/764-touch_dev"
+            }
+            threads {
+              tid: 9876
+              tgid: 9876
+              name: "twoshay"
+            }
+          }
+        }
+        # ==============================================================================
+        # CASE 1: Happy path - complete touch event sequence (TH IRQ -> BH Thread -> Twoshay)
+        # ==============================================================================
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 280000
+              pid: 0
+              irq_handler_entry {
+                irq: 764
+                name: "touch_dev"
+              }
+            }
+            event {
+              timestamp: 282600
+              pid: 0
+              irq_handler_exit {
+                irq: 764
+                ret: 1
+              }
+            }
+          }
+        }
+        packet {
+          ftrace_events {
+            cpu: 0
+            # Counter event (value: 98756)
+            event {
+              timestamp: 281300
+              pid: 0
+              zero {
+                flag: 4 # Counter
+                name: "gti_th_irq_index"
+                pid: 0
+                value: 98756
+              }
+            }
+            # BH parent starts: gti_irq_thread_fn: IRQ_IDX=98756.
+            event {
+              timestamp: 305000
+              pid: 30724
+              zero {
+                flag: 1 # Begin
+                name: "gti_irq_thread_fn: IRQ_IDX=98756."
+                pid: 0
+                value: 0
+              }
+            }
+            # BH child: goog_offload_populate_frame: IDX=98570 IN_TS=0.
+            event {
+              timestamp: 2290000
+              pid: 30724
+              zero {
+                flag: 1 # Begin
+                name: "goog_offload_populate_frame: IDX=98570 IN_TS=0."
+                pid: 0
+                value: 0
+              }
+            }
+            # BH child ends
+            event {
+              timestamp: 2310000
+              pid: 30724
+              zero {
+                flag: 2 # End
+                name: ""
+                pid: 0
+                value: 0
+              }
+            }
+            # BH parent ends
+            event {
+              timestamp: 2325000
+              pid: 30724
+              zero {
+                flag: 2 # End
+                name: ""
+                pid: 0
+                value: 0
+              }
+            }
+            # Twoshay starts: algo->processFrame: INDEX=98570 IN_TS=0.
+            event {
+              timestamp: 2505000
+              pid: 9876
+              print {
+                buf: "B|9876|algo->processFrame: INDEX=98570 IN_TS=0.\n"
+              }
+            }
+            # Resample latency offset counter
+            event {
+              timestamp: 2600000
+              pid: 9876
+              zero {
+                flag: 4 # Counter
+                name: "Resample latency offset"
+                pid: 9876
+                value: 5000000
+              }
+            }
+            # Twoshay ends
+            event {
+              timestamp: 2955000
+              pid: 9876
+              print {
+                buf: "E|9876\n"
+              }
+            }
+          }
+        }
+
+        # ==============================================================================
+        # CASE 2: Missing TH only (ts: 10000000 + offsets)
+        # - Twoshay IN_TS = 10000000
+        # - BH IN_TS = 10000000
+        # - No TH IRQ provided
+        # ==============================================================================
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 10305000
+              pid: 30724
+              zero {
+                flag: 1 # Begin
+                name: "gti_irq_thread_fn: IRQ_IDX=98757."
+                pid: 0
+                value: 0
+              }
+            }
+            event {
+              timestamp: 12290000
+              pid: 30724
+              zero {
+                flag: 1 # Begin
+                name: "goog_offload_populate_frame: IDX=98571 IN_TS=10000000."
+                pid: 0
+                value: 0
+              }
+            }
+            event {
+              timestamp: 12310000
+              pid: 30724
+              zero {
+                flag: 2 # End
+                name: ""
+                pid: 0
+                value: 0
+              }
+            }
+            event {
+              timestamp: 12325000
+              pid: 30724
+              zero {
+                flag: 2 # End
+                name: ""
+                pid: 0
+                value: 0
+              }
+            }
+            event {
+              timestamp: 12505000
+              pid: 9876
+              print {
+                buf: "B|9876|algo->processFrame: INDEX=98571 IN_TS=10000000.\n"
+              }
+            }
+            event {
+              timestamp: 12955000
+              pid: 9876
+              print {
+                buf: "E|9876\n"
+              }
+            }
+          }
+        }
+
+        # ==============================================================================
+        # CASE 3: Missing BH and TH (ts: 20000000 + offsets)
+        # - Only Twoshay event is present
+        # ==============================================================================
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 22505000
+              pid: 9876
+              print {
+                buf: "B|9876|algo->processFrame: INDEX=98572 IN_TS=20000000.\n"
+              }
+            }
+            event {
+              timestamp: 22955000
+              pid: 9876
+              print {
+                buf: "E|9876\n"
+              }
+            }
+          }
+        }
+
+        # ==============================================================================
+        # CASE 4: Counter Outside TH Boundary (ts: 30000000 + offsets)
+        # - TH IRQ is at 30280000 to 30282600
+        # - Counter event is at 30000000 (before TH starts) -> Should NOT join
+        # ==============================================================================
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 30280000
+              pid: 0
+              irq_handler_entry {
+                irq: 764
+                name: "touch_dev"
+              }
+            }
+            event {
+              timestamp: 30282600
+              pid: 0
+              irq_handler_exit {
+                irq: 764
+                ret: 1
+              }
+            }
+          }
+        }
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 30000000
+              pid: 0
+              zero {
+                flag: 4 # Counter
+                name: "gti_th_irq_index"
+                pid: 0
+                value: 98758
+              }
+            }
+            event {
+              timestamp: 30305000
+              pid: 30724
+              zero {
+                flag: 1 # Begin
+                name: "gti_irq_thread_fn: IRQ_IDX=98758."
+                pid: 0
+                value: 0
+              }
+            }
+            event {
+              timestamp: 32290000
+              pid: 30724
+              zero {
+                flag: 1 # Begin
+                name: "goog_offload_populate_frame: IDX=98573 IN_TS=30000000."
+                pid: 0
+                value: 0
+              }
+            }
+            event {
+              timestamp: 32310000
+              pid: 30724
+              zero {
+                flag: 2 # End
+                name: ""
+                pid: 0
+                value: 0
+              }
+            }
+            event {
+              timestamp: 32325000
+              pid: 30724
+              zero {
+                flag: 2 # End
+                name: ""
+                pid: 0
+                value: 0
+              }
+            }
+            event {
+              timestamp: 32505000
+              pid: 9876
+              print {
+                buf: "B|9876|algo->processFrame: INDEX=98573 IN_TS=30000000.\n"
+              }
+            }
+            event {
+              timestamp: 32955000
+              pid: 9876
+              print {
+                buf: "E|9876\n"
+              }
+            }
+          }
+        }
+        """),
+        query="""
+        INCLUDE PERFETTO MODULE pixel.touch;
+
+        SELECT
+          in_ts,
+          ts_pixel_touch_th,
+          dur_pixel_touch_th,
+          ts_pixel_touch_bh,
+          dur_pixel_touch_bh,
+          ts_pixel_touch,
+          dur_pixel_touch,
+          resample_latency_offset
+        FROM pixel_touch_events
+        ORDER BY ts_pixel_touch;
+        """,
+        out=Csv("""
+        "in_ts","ts_pixel_touch_th","dur_pixel_touch_th","ts_pixel_touch_bh","dur_pixel_touch_bh","ts_pixel_touch","dur_pixel_touch","resample_latency_offset"
+        0,280000,2600,305000,2020000,2505000,450000,5000000
+        10000000,"[NULL]","[NULL]",10305000,2020000,12505000,450000,0
+        20000000,"[NULL]","[NULL]","[NULL]","[NULL]",22505000,450000,0
+        30000000,"[NULL]","[NULL]",30305000,2020000,32505000,450000,0
+        """))