Add transition trace processor

Test: tools/ninja -C out/linux_clang_release && tools/diff_test_trace_processor.py ./out/linux_clang_release/trace_processor_shell --name-filter ShellTransitions
Bug: 309630341
Change-Id: I70dcbd421133572a4d36f9d025bf1d67ac208b99
diff --git a/Android.bp b/Android.bp
index 144e1e1..6288761 100644
--- a/Android.bp
+++ b/Android.bp
@@ -11258,6 +11258,8 @@
 filegroup {
     name: "perfetto_src_trace_processor_importers_proto_winscope_full",
     srcs: [
+        "src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc",
+        "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc",
         "src/trace_processor/importers/proto/winscope/winscope_args_parser.cc",
diff --git a/BUILD b/BUILD
index c1d990d..49824c7 100644
--- a/BUILD
+++ b/BUILD
@@ -1576,6 +1576,10 @@
 perfetto_filegroup(
     name = "src_trace_processor_importers_proto_winscope_full",
     srcs = [
+        "src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc",
+        "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h",
+        "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc",
+        "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h",
         "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc",
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index 4751fef..2a3a8b5 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -139,6 +139,12 @@
         context_->storage->mutable_surfaceflinger_transactions_table(), id);
   }
 
+  BoundInserter AddArgsTo(tables::WindowManagerShellTransitionsTable::Id id) {
+    return AddArgsTo(
+        context_->storage->mutable_window_manager_shell_transitions_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/winscope/BUILD.gn b/src/trace_processor/importers/proto/winscope/BUILD.gn
index 2d48f0c..7b04e85 100644
--- a/src/trace_processor/importers/proto/winscope/BUILD.gn
+++ b/src/trace_processor/importers/proto/winscope/BUILD.gn
@@ -16,30 +16,35 @@
 
 source_set("full") {
   sources = [
+    "shell_transitions_parser.cc",
+    "shell_transitions_parser.h",
+    "shell_transitions_tracker.cc",
+    "shell_transitions_tracker.h",
     "surfaceflinger_layers_parser.cc",
     "surfaceflinger_layers_parser.h",
     "surfaceflinger_transactions_parser.cc",
     "surfaceflinger_transactions_parser.h",
-    "winscope_args_parser.h",
     "winscope_args_parser.cc",
+    "winscope_args_parser.h",
     "winscope_module.cc",
     "winscope_module.h",
   ]
   deps = [
     ":gen_cc_winscope_descriptor",
+    "../:proto_importer_module",
     "../../../../../gn:default_deps",
-    "../../../../../protos/perfetto/trace/android:zero",
     "../../../../../protos/perfetto/trace:zero",
+    "../../../../../protos/perfetto/trace/android:zero",
     "../../../storage",
     "../../../tables",
     "../../../types",
     "../../common",
     "../../common:parser_types",
-    "../:proto_importer_module",
   ]
 }
 
 perfetto_cc_proto_descriptor("gen_cc_winscope_descriptor") {
   descriptor_name = "winscope.descriptor"
-  descriptor_target = "../../../../../protos/perfetto/trace/android:winscope_descriptor"
+  descriptor_target =
+      "../../../../../protos/perfetto/trace/android:winscope_descriptor"
 }
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc
new file mode 100644
index 0000000..aee9118
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h"
+#include "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h"
+
+#include "protos/perfetto/trace/android/shell_transition.pbzero.h"
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
+#include "src/trace_processor/importers/proto/winscope/winscope_args_parser.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+ShellTransitionsParser::ShellTransitionsParser(TraceProcessorContext* context)
+    : context_(context), args_parser_{pool_} {
+  pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(),
+                                 kWinscopeDescriptor.size());
+}
+
+void ShellTransitionsParser::Parse(int64_t, protozero::ConstBytes blob) {
+  protos::pbzero::ShellTransition::Decoder transition(blob);
+
+  auto row_id =
+      ShellTransitionsTracker::GetOrCreate(context_)->InternTransition(
+          transition.id());
+
+  auto* window_manager_shell_transitions_table =
+      context_->storage->mutable_window_manager_shell_transitions_table();
+  auto row = window_manager_shell_transitions_table->FindById(row_id).value();
+
+  if (transition.has_dispatch_time_ns()) {
+    row.set_ts(transition.dispatch_time_ns());
+  }
+
+  auto inserter = context_->args_tracker->AddArgsTo(row_id);
+  WinscopeArgsParser writer(inserter, *context_->storage.get());
+  base::Status status = args_parser_.ParseMessage(
+      blob, kShellTransitionsProtoName, nullptr /* parse all fields */, writer);
+
+  if (!status.ok()) {
+    context_->storage->IncrementStats(
+        stats::winscope_shell_transitions_parse_errors);
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h
new file mode 100644
index 0000000..2150e00
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_PARSER_H_
+
+#include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/util/proto_to_args_parser.h"
+
+namespace perfetto {
+
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+class ShellTransitionsParser {
+ public:
+  explicit ShellTransitionsParser(TraceProcessorContext*);
+  void Parse(int64_t timestamp, protozero::ConstBytes);
+
+ private:
+  static constexpr auto* kShellTransitionsProtoName =
+      ".perfetto.protos.ShellTransition";
+
+  TraceProcessorContext* const context_;
+  DescriptorPool pool_;
+  util::ProtoToArgsParser args_parser_;
+};
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_PARSER_H_
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc
new file mode 100644
index 0000000..6025d91
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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 "shell_transitions_tracker.h"
+#include "perfetto/ext/base/crash_keys.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+ShellTransitionsTracker::ShellTransitionsTracker(TraceProcessorContext* context)
+    : context_(context) {}
+
+ShellTransitionsTracker::~ShellTransitionsTracker() = default;
+
+tables::WindowManagerShellTransitionsTable::Id
+ShellTransitionsTracker::InternTransition(int32_t transition_id) {
+  auto pos = transition_id_to_row_mapping_.find(transition_id);
+  if (pos != transition_id_to_row_mapping_.end()) {
+    return pos->second;
+  }
+
+  auto* window_manager_shell_transitions_table =
+      context_->storage->mutable_window_manager_shell_transitions_table();
+
+  tables::WindowManagerShellTransitionsTable::Row row;
+  row.transition_id = transition_id;
+  auto row_id = window_manager_shell_transitions_table->Insert(row).id;
+
+  transition_id_to_row_mapping_.insert({transition_id, row_id});
+
+  return row_id;
+}
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h
new file mode 100644
index 0000000..07ef736
--- /dev/null
+++ b/src/trace_processor/importers/proto/winscope/shell_transitions_tracker.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_TRACKER_H_
+
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Tracks information in the transition table.
+class ShellTransitionsTracker : public Destructible {
+ public:
+  explicit ShellTransitionsTracker(TraceProcessorContext*);
+  virtual ~ShellTransitionsTracker() override;
+
+  static ShellTransitionsTracker* GetOrCreate(TraceProcessorContext* context) {
+    if (!context->shell_transitions_tracker) {
+      context->shell_transitions_tracker.reset(
+          new ShellTransitionsTracker(context));
+    }
+    return static_cast<ShellTransitionsTracker*>(
+        context->shell_transitions_tracker.get());
+  }
+
+  tables::WindowManagerShellTransitionsTable::Id InternTransition(
+      int32_t transition_id);
+
+ private:
+  TraceProcessorContext* context_;
+  std::unordered_map<int32_t, tables::WindowManagerShellTransitionsTable::Id>
+      transition_id_to_row_mapping_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_SHELL_TRANSITIONS_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.cc b/src/trace_processor/importers/proto/winscope/winscope_module.cc
index de9e224..e4d9c0e 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.cc
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.cc
@@ -23,11 +23,13 @@
 
 WinscopeModule::WinscopeModule(TraceProcessorContext* context)
     : surfaceflinger_layers_parser_(context),
-      surfaceflinger_transactions_parser_(context) {
+      surfaceflinger_transactions_parser_(context),
+      shell_transitions_parser_(context) {
   RegisterForField(TracePacket::kSurfaceflingerLayersSnapshotFieldNumber,
                    context);
   RegisterForField(TracePacket::kSurfaceflingerTransactionsFieldNumber,
                    context);
+  RegisterForField(TracePacket::kShellTransitionFieldNumber, context);
 }
 
 void WinscopeModule::ParseTracePacketData(const TracePacket::Decoder& decoder,
@@ -43,6 +45,9 @@
       surfaceflinger_transactions_parser_.Parse(
           timestamp, decoder.surfaceflinger_transactions());
       return;
+    case TracePacket::kShellTransitionFieldNumber:
+      shell_transitions_parser_.Parse(timestamp, decoder.shell_transition());
+      return;
   }
 }
 
diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.h b/src/trace_processor/importers/proto/winscope/winscope_module.h
index d9428d0..fffe42c 100644
--- a/src/trace_processor/importers/proto/winscope/winscope_module.h
+++ b/src/trace_processor/importers/proto/winscope/winscope_module.h
@@ -21,6 +21,7 @@
 #include "perfetto/base/build_config.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h"
 #include "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h"
 #include "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h"
 
@@ -41,6 +42,7 @@
  private:
   SurfaceFlingerLayersParser surfaceflinger_layers_parser_;
   SurfaceFlingerTransactionsParser surfaceflinger_transactions_parser_;
+  ShellTransitionsParser shell_transitions_parser_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index d583632..1e40489 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -262,6 +262,11 @@
                                           kSingle,  kInfo,     kAnalysis,      \
       "SurfaceFlinger transactions packet has unknown fields, which results "  \
       "in some arguments missing. You may need a newer version of trace "      \
+      "processor to parse them."),                                             \
+  F(winscope_shell_transitions_parse_errors,                                   \
+                                          kSingle,  kInfo,     kAnalysis,      \
+      "Shell transition 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
 
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index e75233e..b9413b0 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -738,6 +738,15 @@
     return &surfaceflinger_transactions_table_;
   }
 
+  const tables::WindowManagerShellTransitionsTable&
+  window_manager_shell_transitions_table() const {
+    return window_manager_shell_transitions_table_;
+  }
+  tables::WindowManagerShellTransitionsTable*
+  mutable_window_manager_shell_transitions_table() {
+    return &window_manager_shell_transitions_table_;
+  }
+
   const tables::ExperimentalProtoPathTable& experimental_proto_path_table()
       const {
     return experimental_proto_path_table_;
@@ -995,6 +1004,8 @@
   tables::SurfaceFlingerLayerTable surfaceflinger_layer_table_{&string_pool_};
   tables::SurfaceFlingerTransactionsTable surfaceflinger_transactions_table_{
       &string_pool_};
+  tables::WindowManagerShellTransitionsTable
+      window_manager_shell_transitions_table_{&string_pool_};
 
   tables::ExperimentalProtoPathTable experimental_proto_path_table_{
       &string_pool_};
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 16981fa..e22a56e 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -120,6 +120,8 @@
     default;
 SurfaceFlingerLayerTable::~SurfaceFlingerLayerTable() = default;
 SurfaceFlingerTransactionsTable::~SurfaceFlingerTransactionsTable() = default;
+WindowManagerShellTransitionsTable::~WindowManagerShellTransitionsTable() =
+    default;
 
 }  // namespace tables
 
diff --git a/src/trace_processor/tables/winscope_tables.py b/src/trace_processor/tables/winscope_tables.py
index 22f2080..5391c96 100644
--- a/src/trace_processor/tables/winscope_tables.py
+++ b/src/trace_processor/tables/winscope_tables.py
@@ -60,16 +60,36 @@
         C('arg_set_id', CppUint32()),
     ],
     tabledoc=TableDoc(
-        doc='SurfaceFlinger transactions. Each row contains a set of transactions that SurfaceFlinger committed together.',
+        doc='SurfaceFlinger transactions. Each row contains a set of ' +
+        'transactions that SurfaceFlinger committed together.',
         group='Winscope',
         columns={
             'ts': 'Timestamp of the transactions commit',
             'arg_set_id': 'Extra args parsed from the proto message',
         }))
 
+WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE = Table(
+    python_module=__file__,
+    class_name='WindowManagerShellTransitionsTable',
+    sql_name='window_manager_shell_transitions',
+    columns=[
+        C('ts', CppInt64()),
+        C('transition_id', CppInt64()),
+        C('arg_set_id', CppUint32()),
+    ],
+    tabledoc=TableDoc(
+        doc='Window Manager Shell Transitions',
+        group='Winscope',
+        columns={
+            'ts': 'The timestamp the transition started playing',
+            'transition_id': 'The id of the transition',
+            'arg_set_id': 'Extra args parsed from the proto message',
+        }))
+
 # Keep this list sorted.
 ALL_TABLES = [
     SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE,
     SURFACE_FLINGER_LAYER_TABLE,
     SURFACE_FLINGER_TRANSACTIONS_TABLE,
+    WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE,
 ]
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 6c3b209..8ddff77 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -855,6 +855,8 @@
   RegisterStaticTable(storage->surfaceflinger_layer_table());
   RegisterStaticTable(storage->surfaceflinger_transactions_table());
 
+  RegisterStaticTable(storage->window_manager_shell_transitions_table());
+
   RegisterStaticTable(storage->metadata_table());
   RegisterStaticTable(storage->cpu_table());
   RegisterStaticTable(storage->cpu_freq_table());
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index c2644ee..1eb16e4 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -123,6 +123,7 @@
   std::unique_ptr<Destructible> i2c_tracker;             // I2CTracker
   std::unique_ptr<Destructible> perf_data_tracker;       // PerfDataTracker
   std::unique_ptr<Destructible> content_analyzer;
+  std::unique_ptr<Destructible> shell_transitions_tracker;
 
   // These fields are trace readers which will be called by |forwarding_parser|
   // once the format of the trace is discovered. They are placed here as they
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 28c5abd..f1aed96 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -51,6 +51,7 @@
 from diff_tests.parser.android.tests_games import AndroidGames
 from diff_tests.parser.android.tests_surfaceflinger_layers import SurfaceFlingerLayers
 from diff_tests.parser.android.tests_surfaceflinger_transactions import SurfaceFlingerTransactions
+from diff_tests.parser.android.tests_shell_transitions import ShellTransitions
 from diff_tests.parser.android_fs.tests import AndroidFs
 from diff_tests.parser.atrace.tests import Atrace
 from diff_tests.parser.atrace.tests_error_handling import AtraceErrorHandling
@@ -170,6 +171,8 @@
                             'SurfaceFlingerLayers').fetch(),
       *SurfaceFlingerTransactions(index_path, 'parser/android',
                                   'SurfaceFlingerTransactions').fetch(),
+      *ShellTransitions(index_path, 'parser/android',
+                        'ShellTransitions').fetch(),
       *TrackEvent(index_path, 'parser/track_event', 'TrackEvent').fetch(),
       *TranslatedArgs(index_path, 'parser/translated_args',
                       'TranslatedArgs').fetch(),
diff --git a/test/trace_processor/diff_tests/parser/android/shell_transitions.textproto b/test/trace_processor/diff_tests/parser/android/shell_transitions.textproto
new file mode 100644
index 0000000..b92eb39
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/shell_transitions.textproto
@@ -0,0 +1,167 @@
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  previous_packet_dropped: true
+  trusted_pid: 1305
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 7
+    create_time_ns: 76799049027
+    send_time_ns: 76875395422
+    start_transaction_id: 5604932321952
+    finish_transaction_id: 5604932321954
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1305
+  shell_transition {
+    id: 10
+    create_time_ns: 77854865352
+    send_time_ns: 77894307328
+    start_transaction_id: 5604932322158
+    finish_transaction_id: 5604932322159
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  trusted_pid: 1305
+  shell_transition {
+    id: 11
+    create_time_ns: 82498121051
+    send_time_ns: 82535513345
+    start_transaction_id: 5604932322346
+    finish_transaction_id: 5604932322347
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 3
+  previous_packet_dropped: true
+  trusted_pid: 1305
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 8
+    create_time_ns: 76955664017
+    send_time_ns: 77277756832
+    start_transaction_id: 5604932322028
+    finish_transaction_id: 5604932322029
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 4
+  previous_packet_dropped: true
+  trusted_pid: 1305
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 7
+    starting_window_remove_time_ns: 77706603918
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 5
+  previous_packet_dropped: true
+  trusted_pid: 1305
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 9
+    create_time_ns: 77825423417
+    send_time_ns: 77843436723
+    start_transaction_id: 5604932322137
+    finish_transaction_id: 5604932322138
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 5
+  trusted_pid: 1305
+  shell_transition {
+    id: 9
+    finish_time_ns: 77873935462
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 5
+  trusted_pid: 1305
+  shell_transition {
+    id: 10
+    finish_time_ns: 78621610429
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  previous_packet_dropped: true
+  trusted_pid: 2528
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 7
+    dispatch_time_ns: 76879063147
+    handler: 2
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 8
+    merge_time_ns: 77278725500
+    merge_target: 7
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 8
+    dispatch_time_ns: 77320527177
+    handler: 3
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 9
+    dispatch_time_ns: 77876414832
+    handler: 3
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 10
+    dispatch_time_ns: 77899001013
+    handler: 4
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 11
+    dispatch_time_ns: 82536817137
+    handler: 2
+  }
+}
+packet {
+  trusted_uid: 10241
+  trusted_packet_sequence_id: 6
+  trusted_pid: 2528
+  shell_transition {
+    id: 12
+    merge_time_ns: 82697060749
+    merge_target: 11
+  }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/shell_transitions_simple_merge.textproto b/test/trace_processor/diff_tests/parser/android/shell_transitions_simple_merge.textproto
new file mode 100644
index 0000000..6c9cb65
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/shell_transitions_simple_merge.textproto
@@ -0,0 +1,62 @@
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
+  previous_packet_dropped: true
+  trusted_pid: 1336
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 15
+    create_time_ns: 2187614568227
+    send_time_ns: 2187671767120
+    start_transaction_id: 5738076308937
+    finish_transaction_id: 5738076308938
+    type: 1
+    targets {
+      mode: 1
+      layer_id: 244
+      window_id: 219481253
+      flags: 0
+    }
+    targets {
+      mode: 4
+      layer_id: 47
+      window_id: 54474511
+      flags: 1
+    }
+    flags: 0
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 3
+  previous_packet_dropped: true
+  trusted_pid: 1336
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 15
+    finish_time_ns: 2188195968659
+  }
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 5
+  previous_packet_dropped: true
+  trusted_pid: 1336
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 15
+    starting_window_remove_time_ns: 2188652838898
+  }
+}
+packet {
+  trusted_uid: 10225
+  trusted_packet_sequence_id: 12
+  previous_packet_dropped: true
+  trusted_pid: 3981
+  first_packet_on_sequence: true
+  shell_transition {
+    id: 15
+    dispatch_time_ns: 2187673373973
+    handler: 2
+  }
+}
diff --git a/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py b/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py
new file mode 100644
index 0000000..31af5f7
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/tests_shell_transitions.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 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 ShellTransitions(TestSuite):
+
+  def test_has_expected_transition_rows(self):
+    return DiffTestBlueprint(
+        trace=Path('shell_transitions.textproto'),
+        query="""
+        SELECT
+          id, transition_id
+        FROM
+          window_manager_shell_transitions;
+        """,
+        out=Csv("""
+        "id","transition_id"
+        0,7
+        1,10
+        2,11
+        3,8
+        4,9
+        5,12
+        """))
+
+  def test_has_expected_transition_args(self):
+    return DiffTestBlueprint(
+        trace=Path('shell_transitions_simple_merge.textproto'),
+        query="""
+        SELECT
+          args.key, args.display_value
+        FROM
+          window_manager_shell_transitions JOIN args ON window_manager_shell_transitions.arg_set_id = args.arg_set_id
+        WHERE window_manager_shell_transitions.transition_id = 15
+        ORDER BY args.key;
+        """,
+        out=Csv("""
+        "key","display_value"
+        "create_time_ns","2187614568227"
+        "dispatch_time_ns","2187673373973"
+        "finish_time_ns","2188195968659"
+        "finish_transaction_id","5738076308938"
+        "flags","0"
+        "handler","2"
+        "id","15"
+        "send_time_ns","2187671767120"
+        "start_transaction_id","5738076308937"
+        "starting_window_remove_time_ns","2188652838898"
+        "targets[0].flags","0"
+        "targets[0].layer_id","244"
+        "targets[0].mode","1"
+        "targets[0].window_id","219481253"
+        "targets[1].flags","1"
+        "targets[1].layer_id","47"
+        "targets[1].mode","4"
+        "targets[1].window_id","54474511"
+        "type","1"
+        """))