Add a plugin for Desktop Windowing statsd atoms.

Parse desktop window events into a stdlib table, and
create a plugin that registers a command that will
create a timeline track and pin it.

Bug: 370730027
Test: tools/diff_test_trace_processor.py out/linux/trace_processor_shell --name-filter '.*desktop_mode.*'

Change-Id: Ic8e20529b802c3a25fb0c6858800048f75beb2fc
diff --git a/Android.bp b/Android.bp
index f724ada..861d8ef 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13532,6 +13532,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/cpu/cluster_type.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/device.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/frames/jank_type.sql",
diff --git a/BUILD b/BUILD
index efe83c8..19d13b1 100644
--- a/BUILD
+++ b/BUILD
@@ -2814,6 +2814,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/binder_breakdown.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/device.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/freezer.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
index 40320fa..14a1560 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
@@ -33,6 +33,7 @@
     "binder_breakdown.sql",
     "broadcasts.sql",
     "critical_blocking_calls.sql",
+    "desktop_mode.sql",
     "device.sql",
     "dvfs.sql",
     "freezer.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql b/src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql
new file mode 100644
index 0000000..de4729b
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql
@@ -0,0 +1,72 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+include perfetto module android.statsd;
+
+-- Desktop Windows with durations they were open.
+CREATE PERFETTO TABLE android_desktop_mode_windows (
+-- Window add timestamp; NULL if no add event in the trace.
+raw_add_ts INT,
+-- Window remove timestamp; NULL if no remove event in the trace.
+raw_remove_ts INT,
+-- timestamp that the window was added; or trace_start() if no add event in the trace.
+ts INT,
+-- duration the window was open; or until trace_end() if no remove event in the trace.
+dur INT,
+-- Desktop Window instance ID - unique per window.
+instance_id INT,
+-- UID of the app running in the window.
+uid INT
+) AS
+WITH
+  atoms AS (
+    SELECT
+      ts,
+      extract_arg(arg_set_id, 'field_1') AS type,
+      extract_arg(arg_set_id, 'field_2') AS instance_id,
+      extract_arg(arg_set_id, 'field_3') AS uid
+    FROM android_statsd_atoms
+    WHERE name = 'atom_819'),
+  dw_statsd_events_add AS (
+    SELECT *
+    FROM atoms
+    WHERE type = 1),
+  dw_statsd_events_remove AS (
+    SELECT * FROM atoms
+    WHERE type = 2),
+  dw_statsd_events_update_by_instance AS (
+    SELECT instance_id, min(uid) AS uid FROM atoms
+    WHERE type = 3 GROUP BY instance_id),
+  dw_windows AS (
+    SELECT
+      a.ts AS raw_add_ts,
+      r.ts AS raw_remove_ts,
+      ifnull(a.ts, trace_start()) AS ts,  -- Assume trace_start() if no add event found.
+      ifnull(r.ts, trace_end()) - ifnull(a.ts, trace_start()) AS dur,  -- Assume trace_end() if no remove event found.
+      ifnull(a.instance_id, r.instance_id) AS instance_id,
+      ifnull(a.uid, r.uid) AS uid
+    FROM dw_statsd_events_add a
+    FULL JOIN dw_statsd_events_remove r ON a.instance_id = r.instance_id),
+  -- Assume window was open for the entire trace if we only see change events for the instance ID.
+  dw_windows_with_update_events AS (
+    SELECT * FROM dw_windows
+    UNION
+    SELECT NULL, NULL, trace_start(), trace_end() - trace_start(), instance_id, uid
+    FROM dw_statsd_events_update_by_instance
+    WHERE
+    instance_id NOT IN (SELECT instance_id FROM dw_windows))
+SELECT * FROM dw_windows_with_update_events;
+
diff --git a/test/data/android_desktop_mode/multiple_window_only_update.pb.sha256 b/test/data/android_desktop_mode/multiple_window_only_update.pb.sha256
new file mode 100644
index 0000000..939c986
--- /dev/null
+++ b/test/data/android_desktop_mode/multiple_window_only_update.pb.sha256
@@ -0,0 +1 @@
+de2c99d57ebec6ab843bc56047eb12dbedc3d08a8a1d989d9b3302ed30057286
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/multiple_windows_add_update_remove.pb.sha256 b/test/data/android_desktop_mode/multiple_windows_add_update_remove.pb.sha256
new file mode 100644
index 0000000..5866d07
--- /dev/null
+++ b/test/data/android_desktop_mode/multiple_windows_add_update_remove.pb.sha256
@@ -0,0 +1 @@
+0967fdf494167fe47e5ddf4c540fcc7cc0b22e36ddafa4b4c1172fe3c171b3d6
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/single_window_add_update_no_remove.pb.sha256 b/test/data/android_desktop_mode/single_window_add_update_no_remove.pb.sha256
new file mode 100644
index 0000000..f4656e4
--- /dev/null
+++ b/test/data/android_desktop_mode/single_window_add_update_no_remove.pb.sha256
@@ -0,0 +1 @@
+0bb09b4c68a125a09b6856879af92cd47dd980c2dcaa1513c370b9b6a7b575d1
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/single_window_add_update_remove.pb.sha256 b/test/data/android_desktop_mode/single_window_add_update_remove.pb.sha256
new file mode 100644
index 0000000..3496183
--- /dev/null
+++ b/test/data/android_desktop_mode/single_window_add_update_remove.pb.sha256
@@ -0,0 +1 @@
+bab0c50523ac903872cd11f3fdd4a9ecda98e937573d17011eb877636e82c3c3
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/single_window_no_add_update_remove.pb.sha256 b/test/data/android_desktop_mode/single_window_no_add_update_remove.pb.sha256
new file mode 100644
index 0000000..3c01fec
--- /dev/null
+++ b/test/data/android_desktop_mode/single_window_no_add_update_remove.pb.sha256
@@ -0,0 +1 @@
+7f63f17b65a0fca4bc5ccd75c9a7eb854779585b20cb91fcf371e7892642327e
\ No newline at end of file
diff --git a/test/data/android_desktop_mode/single_window_only_update.pb.sha256 b/test/data/android_desktop_mode/single_window_only_update.pb.sha256
new file mode 100644
index 0000000..20f7be3
--- /dev/null
+++ b/test/data/android_desktop_mode/single_window_only_update.pb.sha256
@@ -0,0 +1 @@
+ee1172b8ad0eaf856e2776575b861f44663bb98f1d40d7d4e66ee0f532220568
\ 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 f13fbb6..9062b8e 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -108,6 +108,7 @@
 from diff_tests.parser.ufs.tests import Ufs
 from diff_tests.parser.zip.tests import Zip
 from diff_tests.stdlib.android.cpu_cluster_tests import CpuClusters
+from diff_tests.stdlib.android.desktop_mode_tests import DesktopMode
 from diff_tests.stdlib.android.frames_tests import Frames
 from diff_tests.stdlib.android.gpu import AndroidGpu
 from diff_tests.stdlib.android.heap_graph_tests import HeapGraph
@@ -295,6 +296,7 @@
       *AndroidGpu(index_path, 'stdlib/android', 'AndroidGpu').fetch(),
       *AndroidStdlib(index_path, 'stdlib/android', 'AndroidStdlib').fetch(),
       *CpuClusters(index_path, 'stdlib/android', 'CpuClusters').fetch(),
+      *DesktopMode(index_path, 'stdlib/android', 'DesktopMode').fetch(),
       *LinuxCpu(index_path, 'stdlib/linux/cpu', 'LinuxCpu').fetch(),
       *LinuxTests(index_path, 'stdlib/linux', 'LinuxTests').fetch(),
       *DominatorTree(index_path, 'stdlib/graphs', 'DominatorTree').fetch(),
diff --git a/test/trace_processor/diff_tests/stdlib/android/desktop_mode_tests.py b/test/trace_processor/diff_tests/stdlib/android/desktop_mode_tests.py
new file mode 100644
index 0000000..0ef52a4
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/android/desktop_mode_tests.py
@@ -0,0 +1,96 @@
+#!/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 DataPath
+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 DesktopMode(TestSuite):
+
+  def test_android_desktop_mode_windows_statsd_events(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_desktop_mode/single_window_add_update_remove.pb'),
+        query="""
+          INCLUDE PERFETTO MODULE android.desktop_mode;
+          SELECT * FROM android_desktop_mode_windows;
+          """,
+        out=Csv("""
+        "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+        1112172132337,1115098491388,1112172132337,2926359051,22,10211
+        """))
+
+  def test_android_desktop_mode_windows_statsd_events_multiple_windows(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_desktop_mode/multiple_windows_add_update_remove.pb'),
+        query="""
+          INCLUDE PERFETTO MODULE android.desktop_mode;
+          SELECT * FROM android_desktop_mode_windows;
+          """,
+        out=Csv("""
+        "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+        1340951146935,1347096280320,1340951146935,6145133385,24,10211
+        1342507511641,1345461733688,1342507511641,2954222047,26,10183
+                """))
+
+  def test_android_desktop_mode_windows_statsd_events_add_no_remove(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_desktop_mode/single_window_add_update_no_remove.pb'),
+        query="""
+          INCLUDE PERFETTO MODULE android.desktop_mode;
+          SELECT * FROM android_desktop_mode_windows;
+          """,
+        out=Csv("""
+        "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+        1552558346094,"[NULL]",1552558346094,1620521485,27,10211
+        """))
+
+  def test_android_desktop_mode_windows_statsd_events_no_add_update_remove(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_desktop_mode/single_window_no_add_update_remove.pb'),
+        query="""
+          INCLUDE PERFETTO MODULE android.desktop_mode;
+          SELECT * FROM android_desktop_mode_windows;
+          """,
+        out=Csv("""
+        "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+        "[NULL]",1696520389866,1695387563286,1132826580,29,10211
+        """))
+
+  def test_android_desktop_mode_windows_statsd_events_only_update(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_desktop_mode/single_window_only_update.pb'),
+        query="""
+          INCLUDE PERFETTO MODULE android.desktop_mode;
+          SELECT * FROM android_desktop_mode_windows;
+          """,
+        out=Csv("""
+        "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+        "[NULL]","[NULL]",1852548597746,3663403770,31,10211
+        """))
+
+  def test_android_desktop_mode_windows_statsd_events_multiple_windows_update_only(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_desktop_mode/multiple_window_only_update.pb'),
+        query="""
+          INCLUDE PERFETTO MODULE android.desktop_mode;
+          SELECT * FROM android_desktop_mode_windows;
+          """,
+        out=Csv("""
+        "raw_add_ts","raw_remove_ts","ts","dur","instance_id","uid"
+        "[NULL]","[NULL]",2137135290268,4737314089,33,10211
+        "[NULL]","[NULL]",2137135290268,4737314089,35,10183
+        """))
+
diff --git a/ui/src/plugins/dev.perfetto.AndroidDesktopMode/OWNERS b/ui/src/plugins/dev.perfetto.AndroidDesktopMode/OWNERS
new file mode 100644
index 0000000..d0be0cc
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidDesktopMode/OWNERS
@@ -0,0 +1,2 @@
+benm@google.com
+
diff --git a/ui/src/plugins/dev.perfetto.AndroidDesktopMode/index.ts b/ui/src/plugins/dev.perfetto.AndroidDesktopMode/index.ts
new file mode 100644
index 0000000..c4fead2
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.AndroidDesktopMode/index.ts
@@ -0,0 +1,81 @@
+// 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.
+
+import {
+  SimpleSliceTrack,
+  SimpleSliceTrackConfig,
+} from '../../frontend/simple_slice_track';
+import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
+import {Trace} from '../../public/trace';
+import {TrackNode} from '../../public/workspace';
+
+const INCLUDE_DESKTOP_MODULE_QUERY = `INCLUDE PERFETTO MODULE android.desktop_mode`;
+
+const QUERY = `
+SELECT
+  ROW_NUMBER() OVER (ORDER BY ts) AS id,
+  ts,
+  dur,
+  ifnull(p.package_name, 'uid=' || dw.uid) AS name
+FROM android_desktop_mode_windows dw
+LEFT JOIN package_list p ON CAST (dw.uid AS INT) % 100000 = p.uid AND p.uid != 1000
+`;
+
+const COLUMNS = ['id', 'ts', 'dur', 'name'];
+const TRACK_NAME = 'Desktop Mode Windows';
+const TRACK_URI = '/desktop_windows';
+
+class AndroidDesktopMode implements PerfettoPlugin {
+  async onTraceReady(_ctx: Trace): Promise<void> {
+    await _ctx.engine.query(INCLUDE_DESKTOP_MODULE_QUERY);
+    this.registerTrack(
+      _ctx,
+      QUERY,
+    );
+    _ctx.commands.registerCommand({
+      id: 'dev.perfetto.DesktopMode#AddTrackDesktopWindowss',
+      name: 'Add Track: ' + TRACK_NAME,
+      callback: () => this.addSimpleTrack(_ctx),
+    });
+  }
+
+  registerTrack(_ctx: Trace, sql: string) {
+    const config: SimpleSliceTrackConfig = {
+      data: {
+        sqlSource: sql,
+        columns: COLUMNS,
+      },
+      columns: {ts: 'ts', dur: 'dur', name: 'name'},
+      argColumns: [],
+    };
+    const track = new SimpleSliceTrack(_ctx, {trackUri: TRACK_URI}, config);
+    _ctx.tracks.registerTrack({
+      uri: TRACK_URI,
+      title: TRACK_NAME,
+      track,
+    });
+  }
+
+  addSimpleTrack(_ctx: Trace) {
+    const trackNode = new TrackNode({uri: TRACK_URI, title: TRACK_NAME});
+    _ctx.workspace.addChildInOrder(trackNode);
+    trackNode.pin();
+  }
+}
+
+export const plugin: PluginDescriptor = {
+  pluginId: 'dev.perfetto.AndroidDesktopMode',
+  plugin: AndroidDesktopMode,
+};
+