tp: resolve UPIDs for android.process_state and track process sequence ID

Add support for UPID resolution and lifecycle reconciliation in the
android_process_state plugin. When dump_process_metadata is enabled and
ftrace is absent, the Android framework acts as the authoritative
source of truth for process tracking, start timestamps, and PID
recycling. When ftrace is present, trace processor defaults to kernel
authority for process boundaries and lifecycles while using snapshot
metadata to enrich canonical process names, UIDs, and start sequence
IDs.

Snapshots and freezer events are processed chronologically during
trace sorting. When boot time to trace clock conversion is available
at packet arrival, process start timestamps are set immediately.
Also add the android_start_seq_id column to the core process table
and views, and add comprehensive unit tests.

TAG=agy
CONV=1301e335-b579-4d4b-b579-e65db5514022
diff --git a/Android.bp b/Android.bp
index 54b52c8..0779df7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -19717,6 +19717,14 @@
     main: "tools/gen_tp_table_headers.py",
 }
 
+// GN: //src/trace_processor/plugins/android_process_state:unittests
+filegroup {
+    name: "perfetto_src_trace_processor_plugins_android_process_state_unittests",
+    srcs: [
+        "src/trace_processor/plugins/android_process_state/android_process_state_tracker_unittest.cc",
+    ],
+}
+
 // GN: //src/trace_processor/plugins/args:args
 filegroup {
     name: "perfetto_src_trace_processor_plugins_args_args",
@@ -24405,6 +24413,7 @@
         ":perfetto_src_trace_processor_plugins_android_job_scheduler_android_job_scheduler",
         ":perfetto_src_trace_processor_plugins_android_job_scheduler_unittests",
         ":perfetto_src_trace_processor_plugins_android_process_state_android_process_state",
+        ":perfetto_src_trace_processor_plugins_android_process_state_unittests",
         ":perfetto_src_trace_processor_plugins_args_args",
         ":perfetto_src_trace_processor_plugins_art_heap_graph_functions_art_heap_graph_functions",
         ":perfetto_src_trace_processor_plugins_art_process_metadata_importer_art_process_metadata_importer",
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 09c1de1..f1776da 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -478,6 +478,7 @@
       "perfetto_sql/tokenizer:unittests",
       "plugins/ancestor:unittests",
       "plugins/android_job_scheduler:unittests",
+      "plugins/android_process_state:unittests",
       "plugins/connected_flow:unittests",
       "plugins/descendant:unittests",
       "plugins/experimental_flat_slice:unittests",
diff --git a/src/trace_processor/importers/common/process_tracker.cc b/src/trace_processor/importers/common/process_tracker.cc
index c99b8f3..e198b12 100644
--- a/src/trace_processor/importers/common/process_tracker.cc
+++ b/src/trace_processor/importers/common/process_tracker.cc
@@ -374,6 +374,40 @@
   // threads once the new mapping is in place (end of function).
   std::optional<UniquePid> recycled_upid;
   if (auto* prev = pids_.Find(pid); prev) {
+    if (use_android_as_source_of_truth_) {
+      auto& pt = *context_->storage->mutable_process_table();
+      auto prr = pt[*prev];
+      if (prr.android_start_seq_id().has_value()) {
+        bool is_zygote_child = false;
+        if (parent_upid) {
+          auto parent_name = pt[*parent_upid].name();
+          if (parent_name.has_value()) {
+            base::StringView name_view =
+                context_->storage->GetString(*parent_name);
+            if (name_view == "zygote" || name_view == "zygote64" ||
+                name_view.StartsWith("app_process")) {
+              is_zygote_child = true;
+            }
+          }
+        }
+        if (parent_upid && !is_zygote_child) {
+          PERFETTO_DLOG(
+              "ProcessTracker: preserving Android framework UPID %u for pid %d "
+              "despite unexpected non-zygote parent %u",
+              *prev, static_cast<int>(pid), *parent_upid);
+        }
+        if (associate_main_thread) {
+          UpdateThread(/*tid=*/pid, pid);
+          if (auto opt_utid = GetThreadOrNull(pid); opt_utid) {
+            UpdateThreadName(*opt_utid, process_name, priority);
+          }
+        }
+        if (parent_upid && !prr.parent_upid().has_value()) {
+          prr.set_parent_upid(*parent_upid);
+        }
+        return *prev;
+      }
+    }
     recycled_upid = *prev;
   }
   pids_.Erase(pid);
@@ -495,6 +529,13 @@
   rr.set_android_user_id(uid / 100000);
 }
 
+void ProcessTracker::SetProcessStartSeqId(UniquePid upid,
+                                          int64_t start_seq_id) {
+  auto& process_table = *context_->storage->mutable_process_table();
+  auto rr = process_table[upid];
+  rr.set_android_start_seq_id(start_seq_id);
+}
+
 void ProcessTracker::SetProcessSortIndex(UniquePid upid,
                                          int32_t sort_index,
                                          SortIndexPriority priority) {
diff --git a/src/trace_processor/importers/common/process_tracker.h b/src/trace_processor/importers/common/process_tracker.h
index 9950d43..924d210 100644
--- a/src/trace_processor/importers/common/process_tracker.h
+++ b/src/trace_processor/importers/common/process_tracker.h
@@ -60,6 +60,7 @@
   kTrackDescriptor = 2,
   kChromeProcessLabelRenderer = 3,
   kSystem = 4,
+  kAndroidFramework = 5,
 };
 
 // Sort index priorities for process and thread tracks. Higher values overwrite
@@ -182,6 +183,18 @@
   // Sets the process user id.
   void SetProcessUid(UniquePid upid, uint32_t uid);
 
+  // Sets the Android start sequence id on the process.
+  void SetProcessStartSeqId(UniquePid upid, int64_t start_seq_id);
+
+  // Configures whether android.process_state is the source of truth for the
+  // process table.
+  void SetUseAndroidAsSourceOfTruth(bool value) {
+    use_android_as_source_of_truth_ = value;
+  }
+  bool UseAndroidAsSourceOfTruth() const {
+    return use_android_as_source_of_truth_;
+  }
+
   // Sets the sort index of a process with priority semantics.
   void SetProcessSortIndex(UniquePid upid,
                            int32_t sort_index,
@@ -394,6 +407,8 @@
   };
   base::FlatHashMap<UniquePid, SortIndex> process_sort_indexes_;
   base::FlatHashMap<UniqueTid, SortIndex> thread_sort_indexes_;
+
+  bool use_android_as_source_of_truth_ = false;
 };
 
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql
index 21fc526..df496ee 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql
@@ -219,6 +219,8 @@
   android_appid LONG,
   -- Android user id of this process.
   android_user_id LONG,
+  -- Android start sequence id of this process.
+  android_start_seq_id LONG,
   -- /proc/cmdline for this process.
   cmdline STRING,
   -- Extra args for this process.
diff --git a/src/trace_processor/plugins/android_framework_track_event/android_framework_track_event.cc b/src/trace_processor/plugins/android_framework_track_event/android_framework_track_event.cc
index df3afae..d1c97f0 100644
--- a/src/trace_processor/plugins/android_framework_track_event/android_framework_track_event.cc
+++ b/src/trace_processor/plugins/android_framework_track_event/android_framework_track_event.cc
@@ -88,9 +88,13 @@
           upid, static_cast<uint32_t>(evt.uid()));
     }
     if (evt.has_process_name()) {
+      auto priority =
+          trace_context_->process_tracker->UseAndroidAsSourceOfTruth()
+              ? ProcessNamePriority::kAndroidFramework
+              : ProcessNamePriority::kOther;
       trace_context_->process_tracker->UpdateProcessName(
           upid, trace_context_->storage->InternString(evt.process_name()),
-          ProcessNamePriority::kOther);
+          priority);
     }
   }
 
@@ -110,13 +114,46 @@
     if (!evt.has_pid()) {
       return;
     }
-    UniquePid upid = trace_context_->process_tracker->GetOrCreateProcess(
-        static_cast<uint32_t>(evt.pid()));
+    UniquePid upid;
+    if (trace_context_->process_tracker->UseAndroidAsSourceOfTruth()) {
+      auto opt_upid = trace_context_->process_tracker->GetProcessOrNull(
+          static_cast<uint32_t>(evt.pid()));
+      if (opt_upid) {
+        auto prr = trace_context_->storage->process_table()[*opt_upid];
+        if (prr.android_start_seq_id().has_value() && evt.has_start_seq_id() &&
+            *prr.android_start_seq_id() != evt.start_seq_id()) {
+          trace_context_->process_tracker->EndThread(
+              ts, static_cast<uint32_t>(evt.pid()));
+          StringId name_id =
+              evt.has_process_name()
+                  ? trace_context_->storage->InternString(evt.process_name())
+                  : kNullStringId;
+          upid = trace_context_->process_tracker->StartNewProcess(
+              ts, std::nullopt, evt.pid(), name_id,
+              ThreadNamePriority::kTrackDescriptor);
+        } else {
+          upid = *opt_upid;
+        }
+      } else {
+        StringId name_id =
+            evt.has_process_name()
+                ? trace_context_->storage->InternString(evt.process_name())
+                : kNullStringId;
+        upid = trace_context_->process_tracker->StartNewProcess(
+            ts, std::nullopt, evt.pid(), name_id,
+            ThreadNamePriority::kTrackDescriptor);
+      }
+    } else {
+      upid = trace_context_->process_tracker->GetOrCreateProcess(
+          static_cast<uint32_t>(evt.pid()));
+    }
     SetProcessMetadata(upid, data);
 
     auto row = GetOrInsertRow(upid);
     if (evt.has_start_seq_id()) {
       row.set_start_seq_id(evt.start_seq_id());
+      trace_context_->process_tracker->SetProcessStartSeqId(upid,
+                                                            evt.start_seq_id());
     }
     if (evt.has_package_uid()) {
       row.set_package_uid(evt.package_uid());
diff --git a/src/trace_processor/plugins/android_process_state/BUILD.gn b/src/trace_processor/plugins/android_process_state/BUILD.gn
index 87f68f5..14985b8 100644
--- a/src/trace_processor/plugins/android_process_state/BUILD.gn
+++ b/src/trace_processor/plugins/android_process_state/BUILD.gn
@@ -14,6 +14,7 @@
 
 import("../../../../gn/perfetto.gni")
 import("../../../../gn/perfetto_tp_tables.gni")
+import("../../../../gn/test.gni")
 
 assert(enable_perfetto_trace_processor_sqlite)
 
@@ -36,6 +37,8 @@
     "../../../../gn:default_deps",
     "../../../../include/perfetto/ext/base:base",
     "../../../../protos/perfetto/common:zero",
+    "../../../../protos/perfetto/config:zero",
+    "../../../../protos/perfetto/config/android:zero",
     "../../../../protos/perfetto/trace:zero",
     "../../../../protos/third_party/android/frameworks/base/proto/tracing:frameworks_base_common_zero",
     "../../../../protos/third_party/android/frameworks/base/proto/tracing:frameworks_base_trace_packet_zero",
@@ -53,3 +56,23 @@
     "../../util:descriptors",
   ]
 }
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  deps = [
+    ":android_process_state",
+    ":tables",
+    "../../../../gn:default_deps",
+    "../../../../gn:gtest_and_gmock",
+    "../../../../protos/perfetto/config:zero",
+    "../../../../protos/perfetto/config/android:zero",
+    "../../../../protos/perfetto/trace:zero",
+    "../../../../protos/third_party/android/frameworks/base/proto/tracing:frameworks_base_trace_packet_zero",
+    "../../../../src/protozero",
+    "../../importers/common",
+    "../../importers/proto:minimal",
+    "../../storage",
+    "../../types",
+  ]
+  sources = [ "android_process_state_tracker_unittest.cc" ]
+}
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_module.cc b/src/trace_processor/plugins/android_process_state/android_process_state_module.cc
index 783d6cd..4f017dc 100644
--- a/src/trace_processor/plugins/android_process_state/android_process_state_module.cc
+++ b/src/trace_processor/plugins/android_process_state/android_process_state_module.cc
@@ -23,6 +23,9 @@
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
+#include "protos/perfetto/config/android/android_process_state_config.pbzero.h"
+#include "protos/perfetto/config/data_source_config.pbzero.h"
+#include "protos/perfetto/config/trace_config.pbzero.h"
 #include "protos/third_party/android/frameworks/base/proto/tracing/frameworks_base_trace_packet.pbzero.h"
 #include "protos/third_party/android/frameworks/base/proto/tracing/frameworks_base_track_event.pbzero.h"
 
@@ -47,11 +50,13 @@
   switch (args.field.id()) {
     case fb::FrameworksBaseTracePacket::kAndroidProcessStateFieldNumber:
       tracker_->ParseProcessStateDump(
+          args.ts,
           args.field
               .Cast<fb::FrameworksBaseTracePacket::kAndroidProcessState>());
       break;
     case fb::FrameworksBaseTracePacket::kAndroidFreezerStateFieldNumber:
       tracker_->ParseFreezerDump(
+          args.ts,
           args.field
               .Cast<fb::FrameworksBaseTracePacket::kAndroidFreezerState>());
       break;
@@ -60,6 +65,35 @@
   }
 }
 
+void AndroidProcessStateModule::ParseTraceConfig(
+    const protos::pbzero::TraceConfig_Decoder& trace_config) {
+  bool ftrace_configured = false;
+  std::optional<bool> dump_process_metadata;
+
+  for (auto it = trace_config.data_sources(); it; ++it) {
+    protos::pbzero::TraceConfig::DataSource::Decoder ds(*it);
+    if (!ds.has_config()) {
+      continue;
+    }
+    protos::pbzero::DataSourceConfig::Decoder cfg(ds.config());
+    if (cfg.name().ToStdStringView() == "linux.ftrace") {
+      ftrace_configured = true;
+    }
+    if (cfg.name().ToStdStringView() == "android.process_state") {
+      bool dump_meta = false;
+      if (cfg.has_android_process_state_config()) {
+        protos::pbzero::AndroidProcessStateConfig::Decoder aps_cfg(
+            cfg.android_process_state_config());
+        dump_meta = aps_cfg.has_dump_process_metadata() &&
+                    aps_cfg.dump_process_metadata();
+      }
+      dump_process_metadata = dump_meta;
+    }
+  }
+
+  tracker_->OnConfigDetected(ftrace_configured, dump_process_metadata);
+}
+
 void AndroidProcessStateModule::OnEventsFullyExtracted() {
   tracker_->Finalize();
 }
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_module.h b/src/trace_processor/plugins/android_process_state/android_process_state_module.h
index f605cba..3ff6fbf 100644
--- a/src/trace_processor/plugins/android_process_state/android_process_state_module.h
+++ b/src/trace_processor/plugins/android_process_state/android_process_state_module.h
@@ -36,6 +36,8 @@
   ~AndroidProcessStateModule() override;
 
   void ParseField(const ParseFieldArgs& args) override;
+  void ParseTraceConfig(
+      const protos::pbzero::TraceConfig_Decoder& trace_config) override;
   void OnEventsFullyExtracted() override;
 
  private:
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_tracker.cc b/src/trace_processor/plugins/android_process_state/android_process_state_tracker.cc
index a2cf1b0..f8d7e3f 100644
--- a/src/trace_processor/plugins/android_process_state/android_process_state_tracker.cc
+++ b/src/trace_processor/plugins/android_process_state/android_process_state_tracker.cc
@@ -19,6 +19,8 @@
 #include <string>
 
 #include "perfetto/ext/base/string_view.h"
+#include "protos/perfetto/common/builtin_clock.pbzero.h"
+#include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
@@ -36,8 +38,10 @@
                     DescriptorPool::CachedDescriptor& cache,
                     const char* enum_name,
                     int32_t value) {
-  std::optional<std::string> name =
-      context->descriptor_pool_->FindEnumString(cache, enum_name, value);
+  std::optional<std::string> name;
+  if (context->descriptor_pool_) {
+    name = context->descriptor_pool_->FindEnumString(cache, enum_name, value);
+  }
   return context->storage->InternString(
       base::StringView(name ? *name : std::to_string(value)));
 }
@@ -112,7 +116,144 @@
   process_state_table_->Insert(row);
 }
 
+void AndroidProcessStateTracker::OnConfigDetected(
+    bool ftrace_configured,
+    std::optional<bool> dump_process_metadata) {
+  if (ftrace_configured) {
+    ftrace_configured_ = true;
+  } else if (!ftrace_configured_.has_value()) {
+    ftrace_configured_ = false;
+  }
+
+  if (dump_process_metadata.value_or(false)) {
+    dump_process_metadata_configured_ = true;
+  } else if (!dump_process_metadata_configured_.has_value() &&
+             dump_process_metadata.has_value()) {
+    dump_process_metadata_configured_ = false;
+  }
+
+  context_->process_tracker->SetUseAndroidAsSourceOfTruth(
+      !HasFtrace() && dump_process_metadata_configured_.value_or(false));
+}
+
+bool AndroidProcessStateTracker::HasFtrace() const {
+  if (context_->storage->ftrace_event_table().row_count() > 0) {
+    return true;
+  }
+  return ftrace_configured_.value_or(false);
+}
+
+bool AndroidProcessStateTracker::HasProcessMetadata(
+    const RecordDecoder& rec) const {
+  if (dump_process_metadata_configured_.has_value()) {
+    return *dump_process_metadata_configured_;
+  }
+  return rec.has_process_name() || rec.has_start_seq_id() ||
+         rec.has_start_time_ms();
+}
+
+AndroidProcessStateTracker::UpidResolutionMode
+AndroidProcessStateTracker::DetermineResolutionMode(
+    const RecordDecoder& rec) const {
+  if (!HasFtrace() && HasProcessMetadata(rec)) {
+    return UpidResolutionMode::kUserspaceAuthority;
+  }
+  return UpidResolutionMode::kKernelAuthority;
+}
+
+UniquePid AndroidProcessStateTracker::ResolveUserspaceAuthority(
+    int64_t ts,
+    const RecordDecoder& rec) {
+  std::optional<int64_t> opt_start_ts;
+  if (rec.has_start_time_ms() && context_->clock_tracker) {
+    int64_t boottime_ns = rec.start_time_ms() * 1000000LL;
+    opt_start_ts = context_->clock_tracker->ToTraceTime(
+        ClockId::Machine(protos::pbzero::BUILTIN_CLOCK_BOOTTIME), boottime_ns);
+  }
+
+  auto opt_upid = context_->process_tracker->GetProcessOrNull(
+      static_cast<uint32_t>(rec.pid()));
+  UniquePid upid;
+  if (opt_upid.has_value()) {
+    auto prr = context_->storage->process_table()[*opt_upid];
+    if (prr.android_start_seq_id().has_value() && rec.has_start_seq_id() &&
+        *prr.android_start_seq_id() != rec.start_seq_id()) {
+      // Start sequence id mismatch indicates PID reuse.
+      // Compute end_ts for preceding process incarnation:
+      // Prefer opt_start_ts if available and >= old start_ts; otherwise
+      // fallback to ts.
+      int64_t end_ts = ts;
+      if (opt_start_ts.has_value()) {
+        auto prev_start_ts = prr.start_ts();
+        if (!prev_start_ts.has_value() || *opt_start_ts >= *prev_start_ts) {
+          end_ts = *opt_start_ts;
+        }
+      }
+
+      context_->process_tracker->EndThread(end_ts,
+                                           static_cast<uint32_t>(rec.pid()));
+      StringId name_id = rec.has_process_name()
+                             ? context_->storage->InternString(
+                                   base::StringView(rec.process_name()))
+                             : kNullStringId;
+      upid = context_->process_tracker->StartNewProcess(
+          opt_start_ts, std::nullopt, rec.pid(), name_id,
+          ThreadNamePriority::kTrackDescriptor);
+    } else {
+      upid = *opt_upid;
+    }
+  } else {
+    StringId name_id = rec.has_process_name()
+                           ? context_->storage->InternString(
+                                 base::StringView(rec.process_name()))
+                           : kNullStringId;
+    upid = context_->process_tracker->StartNewProcess(
+        opt_start_ts, std::nullopt, rec.pid(), name_id,
+        ThreadNamePriority::kTrackDescriptor);
+  }
+
+  if (rec.has_process_name()) {
+    StringId name_id =
+        context_->storage->InternString(base::StringView(rec.process_name()));
+    context_->process_tracker->UpdateProcessName(
+        upid, name_id, ProcessNamePriority::kAndroidFramework);
+  }
+  if (rec.has_uid()) {
+    context_->process_tracker->SetProcessUid(upid,
+                                             static_cast<uint32_t>(rec.uid()));
+  }
+  if (rec.has_start_seq_id()) {
+    context_->process_tracker->SetProcessStartSeqId(upid, rec.start_seq_id());
+  }
+  if (opt_start_ts.has_value()) {
+    context_->process_tracker->SetStartTsIfUnset(upid, *opt_start_ts);
+  }
+
+  return upid;
+}
+
+UniquePid AndroidProcessStateTracker::ResolveKernelAuthority(
+    const RecordDecoder& rec) {
+  UniquePid upid = context_->process_tracker->GetOrCreateProcess(
+      static_cast<uint32_t>(rec.pid()));
+  if (rec.has_process_name()) {
+    StringId name_id =
+        context_->storage->InternString(base::StringView(rec.process_name()));
+    context_->process_tracker->UpdateProcessName(upid, name_id,
+                                                 ProcessNamePriority::kSystem);
+  }
+  if (rec.has_uid()) {
+    context_->process_tracker->SetProcessUid(upid,
+                                             static_cast<uint32_t>(rec.uid()));
+  }
+  if (rec.has_start_seq_id()) {
+    context_->process_tracker->SetProcessStartSeqId(upid, rec.start_seq_id());
+  }
+  return upid;
+}
+
 void AndroidProcessStateTracker::ParseProcessStateDump(
+    int64_t ts,
     protozero::ConstBytes blob) {
   fb::AndroidProcessStateSnapshot::Decoder dump(blob);
   for (auto it = dump.record(); it; ++it) {
@@ -120,16 +261,24 @@
     if (!rec.has_pid()) {
       continue;
     }
-    ProcessStateValues v;
-    v.upid = context_->process_tracker->GetOrCreateProcess(
-        static_cast<uint32_t>(rec.pid()));
 
-    // Note: android.util.proto.ProtoOutputStream ignores/omits 0 data points
-    // during serialization on Android, so unset fields in the dump snapshot
-    // represent 0.
-    //
-    // TODO: Consider setting process_name and uid on the core `process` table
-    // from dump records in a future update.
+    if (!dump_process_metadata_configured_.has_value() &&
+        (rec.has_process_name() || rec.has_start_seq_id() ||
+         rec.has_start_time_ms())) {
+      dump_process_metadata_configured_ = true;
+      context_->process_tracker->SetUseAndroidAsSourceOfTruth(!HasFtrace());
+    }
+
+    UpidResolutionMode mode = DetermineResolutionMode(rec);
+    UniquePid upid;
+    if (mode == UpidResolutionMode::kUserspaceAuthority) {
+      upid = ResolveUserspaceAuthority(ts, rec);
+    } else {
+      upid = ResolveKernelAuthority(rec);
+    }
+
+    ProcessStateValues v;
+    v.upid = upid;
     v.proc_state = rec.has_proc_state()
                        ? static_cast<int32_t>(rec.proc_state())
                        : static_cast<int32_t>(
@@ -168,7 +317,8 @@
   freezer_state_table_->Insert(row);
 }
 
-void AndroidProcessStateTracker::ParseFreezerDump(protozero::ConstBytes blob) {
+void AndroidProcessStateTracker::ParseFreezerDump(int64_t /*ts*/,
+                                                  protozero::ConstBytes blob) {
   fb::AndroidFreezerStateSnapshot::Decoder dump(blob);
   for (auto it = dump.record(); it; ++it) {
     fb::AndroidFreezerStateSnapshot::Record::Decoder rec(*it);
@@ -178,8 +328,6 @@
     FreezerStateValues v;
     v.upid = context_->process_tracker->GetOrCreateProcess(
         static_cast<uint32_t>(rec.pid()));
-    // Note: android.util.proto.ProtoOutputStream ignores/omits 0 data points
-    // during serialization on Android, so unset fields represent UFR_NONE (0).
     v.unfreeze_reason =
         rec.has_unfreeze_reason()
             ? static_cast<int32_t>(rec.unfreeze_reason())
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_tracker.h b/src/trace_processor/plugins/android_process_state/android_process_state_tracker.h
index 70d46a3..9f65eae 100644
--- a/src/trace_processor/plugins/android_process_state/android_process_state_tracker.h
+++ b/src/trace_processor/plugins/android_process_state/android_process_state_tracker.h
@@ -21,12 +21,18 @@
 #include <limits>
 #include <map>
 #include <optional>
+#include <string>
+#include <vector>
 
 #include "perfetto/protozero/field.h"
 #include "src/trace_processor/plugins/android_process_state/tables_py.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/util/descriptors.h"
 
+namespace com::android::internal::pbzero {
+class AndroidProcessStateSnapshot_Record_Decoder;
+}  // namespace com::android::internal::pbzero
+
 namespace perfetto::trace_processor {
 class TraceProcessorContext;
 }  // namespace perfetto::trace_processor
@@ -50,13 +56,48 @@
 
   // A process_state_changed_event TrackEvent extension at |ts|.
   void ParseProcessStateChange(int64_t ts, protozero::ConstBytes bytes);
-  // An AndroidProcessState dump TracePacket.
-  void ParseProcessStateDump(protozero::ConstBytes bytes);
+  // An AndroidProcessState dump TracePacket at |ts|.
+  void ParseProcessStateDump(int64_t ts, protozero::ConstBytes bytes);
 
   // An AndroidFreezerEvent TrackEvent extension at |ts|.
   void ParseFreezerEvent(int64_t ts, protozero::ConstBytes bytes);
-  // An AndroidFreezerState dump TracePacket.
-  void ParseFreezerDump(protozero::ConstBytes bytes);
+  // An AndroidFreezerState dump TracePacket at |ts|.
+  void ParseFreezerDump(int64_t ts, protozero::ConstBytes bytes);
+
+  using RecordDecoder = ::com::android::internal::pbzero::
+      AndroidProcessStateSnapshot_Record_Decoder;
+
+  enum class UpidResolutionMode {
+    // Only dump_process_metadata is active (no ftrace): Android framework is
+    // the authoritative source of truth for process creation, start_ts, and
+    // PID reuse.
+    kUserspaceAuthority,
+
+    // Kernel / Ftrace authority (default).
+    // Active whenever ftrace is present (even if dump_process_metadata is
+    // enabled)
+    // or when dump_process_metadata is disabled/absent.
+    kKernelAuthority,
+  };
+
+  // Receives parsed config notification from TraceConfig.
+  void OnConfigDetected(bool ftrace_configured,
+                        std::optional<bool> dump_process_metadata);
+
+  // Queries whether Ftrace is active in this trace (either via config or
+  // detected in storage).
+  bool HasFtrace() const;
+
+  // Queries whether process metadata is enabled/present in this trace (either
+  // via config or detected in snapshot records).
+  bool HasProcessMetadata(const RecordDecoder& rec) const;
+
+  // Determines the UPID resolution mode based on config and data presence.
+  UpidResolutionMode DetermineResolutionMode(const RecordDecoder& rec) const;
+
+  // Resolves the UPID for a dump record based on the active mode:
+  UniquePid ResolveUserspaceAuthority(int64_t ts, const RecordDecoder& rec);
+  UniquePid ResolveKernelAuthority(const RecordDecoder& rec);
 
   // Synthesizes the per-process initial-state rows.
   void Finalize();
@@ -115,6 +156,9 @@
   std::map<UniquePid, ProcessStateValues> process_dump_;
   // Map of upid -> final freezer state from the trace-stop dump snapshot.
   std::map<UniquePid, FreezerStateValues> freezer_dump_;
+
+  std::optional<bool> ftrace_configured_;
+  std::optional<bool> dump_process_metadata_configured_;
 };
 
 }  // namespace perfetto::trace_processor::android_process_state
diff --git a/src/trace_processor/plugins/android_process_state/android_process_state_tracker_unittest.cc b/src/trace_processor/plugins/android_process_state/android_process_state_tracker_unittest.cc
new file mode 100644
index 0000000..0cd8a79
--- /dev/null
+++ b/src/trace_processor/plugins/android_process_state/android_process_state_tracker_unittest.cc
@@ -0,0 +1,399 @@
+/*
+ * 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 "src/trace_processor/plugins/android_process_state/android_process_state_tracker.h"
+
+#include <memory>
+#include <string>
+
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "src/trace_processor/importers/common/machine_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/plugins/android_process_state/android_process_state_module.h"
+#include "src/trace_processor/plugins/android_process_state/tables_py.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/config/android/android_process_state_config.pbzero.h"
+#include "protos/perfetto/config/data_source_config.pbzero.h"
+#include "protos/perfetto/config/trace_config.pbzero.h"
+#include "protos/third_party/android/frameworks/base/proto/tracing/frameworks_base_trace_packet.pbzero.h"
+
+namespace perfetto::trace_processor::android_process_state {
+namespace {
+
+using tables::AndroidFreezerStateTable;
+using tables::AndroidProcessStateTable;
+using RecordDecoder = AndroidProcessStateTracker::RecordDecoder;
+namespace fb = com::android::internal::pbzero;
+
+class AndroidProcessStateTrackerTest : public ::testing::Test {
+ public:
+  AndroidProcessStateTrackerTest() {
+    context_.storage.reset(new TraceStorage());
+    context_.machine_tracker.reset(new MachineTracker(&context_, 0));
+    context_.process_tracker.reset(new ProcessTracker(&context_));
+    context_.descriptor_pool_.reset(new DescriptorPool());
+    process_state_table_ = std::make_unique<AndroidProcessStateTable>(
+        context_.storage->mutable_string_pool());
+    freezer_state_table_ = std::make_unique<AndroidFreezerStateTable>(
+        context_.storage->mutable_string_pool());
+    tracker_ = std::make_unique<AndroidProcessStateTracker>(
+        &context_, process_state_table_.get(), freezer_state_table_.get());
+  }
+
+ protected:
+  TraceProcessorContext context_;
+  std::unique_ptr<AndroidProcessStateTable> process_state_table_;
+  std::unique_ptr<AndroidFreezerStateTable> freezer_state_table_;
+  std::unique_ptr<AndroidProcessStateTracker> tracker_;
+};
+
+TEST_F(AndroidProcessStateTrackerTest, CoexistenceDefaultsToKernelAuthority) {
+  // Both ftrace and dump_process_metadata configured: defaults to
+  // kKernelAuthority.
+  tracker_->OnConfigDetected(/*ftrace_configured=*/true,
+                             /*dump_process_metadata=*/true);
+  EXPECT_TRUE(tracker_->HasFtrace());
+
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot::Record> record;
+  record->set_pid(1001);
+  record->set_process_name("com.example.app");
+  record->set_start_seq_id(42);
+  record->set_start_time_ms(123456);
+  std::string bytes = record.SerializeAsString();
+  fb::AndroidProcessStateSnapshot::Record::Decoder rec(bytes);
+
+  EXPECT_TRUE(tracker_->HasProcessMetadata(rec));
+  EXPECT_EQ(tracker_->DetermineResolutionMode(rec),
+            AndroidProcessStateTracker::UpidResolutionMode::kKernelAuthority);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, UserspaceAuthorityMode) {
+  // Only dump_process_metadata configured, no ftrace.
+  tracker_->OnConfigDetected(/*ftrace_configured=*/false,
+                             /*dump_process_metadata=*/true);
+  EXPECT_FALSE(tracker_->HasFtrace());
+
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot::Record> record;
+  record->set_pid(1001);
+  record->set_process_name("com.example.app");
+  record->set_start_seq_id(42);
+  record->set_start_time_ms(123456);
+  std::string bytes = record.SerializeAsString();
+  fb::AndroidProcessStateSnapshot::Record::Decoder rec(bytes);
+
+  EXPECT_TRUE(tracker_->HasProcessMetadata(rec));
+  EXPECT_EQ(
+      tracker_->DetermineResolutionMode(rec),
+      AndroidProcessStateTracker::UpidResolutionMode::kUserspaceAuthority);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, KernelAuthorityModeDisabledFlag) {
+  // dump_process_metadata explicitly false.
+  tracker_->OnConfigDetected(/*ftrace_configured=*/true,
+                             /*dump_process_metadata=*/false);
+  EXPECT_TRUE(tracker_->HasFtrace());
+
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot::Record> record;
+  record->set_pid(1001);
+  std::string bytes = record.SerializeAsString();
+  fb::AndroidProcessStateSnapshot::Record::Decoder rec(bytes);
+
+  EXPECT_FALSE(tracker_->HasProcessMetadata(rec));
+  EXPECT_EQ(tracker_->DetermineResolutionMode(rec),
+            AndroidProcessStateTracker::UpidResolutionMode::kKernelAuthority);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, FallbackFtraceFromStorage) {
+  // No TraceConfig parsed, but storage has ftrace rows: defaults to
+  // kKernelAuthority.
+  EXPECT_FALSE(tracker_->HasFtrace());
+  tables::FtraceEventTable::Row ftrace_row;
+  context_.storage->mutable_ftrace_event_table()->Insert(ftrace_row);
+  EXPECT_TRUE(tracker_->HasFtrace());
+
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot::Record> record;
+  record->set_pid(1001);
+  record->set_process_name("com.example.app");
+  std::string bytes = record.SerializeAsString();
+  fb::AndroidProcessStateSnapshot::Record::Decoder rec(bytes);
+
+  EXPECT_TRUE(tracker_->HasProcessMetadata(rec));
+  EXPECT_EQ(tracker_->DetermineResolutionMode(rec),
+            AndroidProcessStateTracker::UpidResolutionMode::kKernelAuthority);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, FallbackRecordMetadataInspection) {
+  // No TraceConfig parsed, no ftrace. Record has metadata.
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot::Record> record;
+  record->set_pid(1001);
+  record->set_process_name("com.example.app");
+  std::string bytes = record.SerializeAsString();
+  fb::AndroidProcessStateSnapshot::Record::Decoder rec(bytes);
+
+  EXPECT_TRUE(tracker_->HasProcessMetadata(rec));
+  EXPECT_EQ(
+      tracker_->DetermineResolutionMode(rec),
+      AndroidProcessStateTracker::UpidResolutionMode::kUserspaceAuthority);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, SystemServerZeroSuppressionHandling) {
+  // Configured with dump_process_metadata = true.
+  // system_server has startSeq=0 and startElapsedTime=0 (suppressed on wire),
+  // but has process_name.
+  tracker_->OnConfigDetected(/*ftrace_configured=*/false,
+                             /*dump_process_metadata=*/true);
+
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot::Record> record;
+  record->set_pid(1000);
+  record->set_process_name("system_server");
+  std::string bytes = record.SerializeAsString();
+  fb::AndroidProcessStateSnapshot::Record::Decoder rec(bytes);
+
+  EXPECT_FALSE(rec.has_start_seq_id());
+  EXPECT_FALSE(rec.has_start_time_ms());
+  EXPECT_TRUE(rec.has_process_name());
+  EXPECT_TRUE(tracker_->HasProcessMetadata(rec));
+  EXPECT_EQ(
+      tracker_->DetermineResolutionMode(rec),
+      AndroidProcessStateTracker::UpidResolutionMode::kUserspaceAuthority);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, ParseTraceConfigViaModule) {
+  ProtoImporterModuleContext module_context;
+  AndroidProcessStateModule module(&module_context, tracker_.get());
+
+  protozero::HeapBuffered<protos::pbzero::TraceConfig> config;
+  {
+    auto* ds = config->add_data_sources();
+    auto* ds_cfg = ds->set_config();
+    ds_cfg->set_name("linux.ftrace");
+  }
+  {
+    auto* ds = config->add_data_sources();
+    auto* ds_cfg = ds->set_config();
+    ds_cfg->set_name("android.process_state");
+    auto* aps_cfg = ds_cfg->set_android_process_state_config();
+    aps_cfg->set_dump_process_metadata(true);
+  }
+
+  std::string config_bytes = config.SerializeAsString();
+  protos::pbzero::TraceConfig_Decoder trace_config_decoder(config_bytes);
+  module.ParseTraceConfig(trace_config_decoder);
+
+  EXPECT_TRUE(tracker_->HasFtrace());
+
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot::Record> record;
+  record->set_pid(1001);
+  record->set_process_name("com.example.app");
+  std::string bytes = record.SerializeAsString();
+  fb::AndroidProcessStateSnapshot::Record::Decoder rec(bytes);
+
+  EXPECT_TRUE(tracker_->HasProcessMetadata(rec));
+  EXPECT_EQ(tracker_->DetermineResolutionMode(rec),
+            AndroidProcessStateTracker::UpidResolutionMode::kKernelAuthority);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, ConfigLatching) {
+  // First config enables metadata.
+  tracker_->OnConfigDetected(/*ftrace_configured=*/false,
+                             /*dump_process_metadata=*/true);
+  EXPECT_TRUE(tracker_->HasProcessMetadata(
+      RecordDecoder(protozero::ConstBytes{nullptr, 0})));
+
+  // Secondary config without metadata or with false does not overwrite.
+  tracker_->OnConfigDetected(/*ftrace_configured=*/false,
+                             /*dump_process_metadata=*/false);
+  EXPECT_TRUE(tracker_->HasProcessMetadata(
+      RecordDecoder(protozero::ConstBytes{nullptr, 0})));
+}
+
+TEST_F(AndroidProcessStateTrackerTest, ReconcileUserspaceAuthority) {
+  tracker_->OnConfigDetected(/*ftrace_configured=*/false,
+                             /*dump_process_metadata=*/true);
+
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot> snapshot;
+  auto* rec = snapshot->add_record();
+  rec->set_pid(2001);
+  rec->set_uid(10050);
+  rec->set_process_name("com.test.app");
+  rec->set_start_seq_id(100);
+  rec->set_start_time_ms(5000);
+  rec->set_proc_state(1002);
+  rec->set_oom_score(100);
+
+  std::string bytes = snapshot.SerializeAsString();
+  tracker_->ParseProcessStateDump(
+      1000, protozero::ConstBytes{
+                reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size()});
+
+  tracker_->Finalize();
+
+  auto opt_upid = context_.process_tracker->GetProcessOrNull(2001);
+  ASSERT_TRUE(opt_upid.has_value());
+  auto prr = context_.storage->process_table()[*opt_upid];
+  EXPECT_EQ(prr.pid(), 2001u);
+  EXPECT_EQ(context_.storage->GetString(*prr.name()), "com.test.app");
+  EXPECT_EQ(prr.android_user_id(), 0u);
+  EXPECT_EQ(prr.android_start_seq_id(), 100);
+
+  EXPECT_EQ(process_state_table_->row_count(), 1u);
+  auto state_row = (*process_state_table_)[0];
+  EXPECT_EQ(state_row.upid(), *opt_upid);
+  EXPECT_EQ(state_row.is_initial(), 1u);
+  EXPECT_EQ(state_row.oom_score(), 100);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, ReconcileUserspacePidRecycling) {
+  tracker_->OnConfigDetected(/*ftrace_configured=*/false,
+                             /*dump_process_metadata=*/true);
+
+  // Initial process for PID 2001 with start_seq_id 10.
+  UniquePid old_upid = context_.process_tracker->StartNewProcess(
+      1000, std::nullopt, 2001,
+      context_.storage->InternString("com.test.oldapp"),
+      ThreadNamePriority::kTrackDescriptor);
+  context_.process_tracker->SetProcessStartSeqId(old_upid, 10);
+
+  // New dump record for PID 2001 with different start_seq_id 20 arriving at
+  // ts=2000.
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot> snapshot;
+  auto* rec = snapshot->add_record();
+  rec->set_pid(2001);
+  rec->set_uid(10060);
+  rec->set_process_name("com.test.newapp");
+  rec->set_start_seq_id(20);
+  rec->set_start_time_ms(8000);
+  rec->set_proc_state(1003);
+
+  std::string bytes = snapshot.SerializeAsString();
+  tracker_->ParseProcessStateDump(
+      2000, protozero::ConstBytes{
+                reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size()});
+
+  tracker_->Finalize();
+
+  auto opt_upid = context_.process_tracker->GetProcessOrNull(2001);
+  ASSERT_TRUE(opt_upid.has_value());
+  EXPECT_NE(*opt_upid, old_upid);
+
+  auto old_prr = context_.storage->process_table()[old_upid];
+  EXPECT_TRUE(old_prr.end_ts().has_value());
+  EXPECT_EQ(*old_prr.end_ts(), 2000);
+
+  auto new_prr = context_.storage->process_table()[*opt_upid];
+  EXPECT_EQ(context_.storage->GetString(*new_prr.name()), "com.test.newapp");
+  EXPECT_EQ(new_prr.android_start_seq_id(), 20);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, ChronologicalMidTraceLifecycle) {
+  tracker_->OnConfigDetected(/*ftrace_configured=*/false,
+                             /*dump_process_metadata=*/true);
+
+  // Snapshot arrives at ts = 1000 for App A (start_seq_id 1).
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot> snapshot;
+  auto* rec = snapshot->add_record();
+  rec->set_pid(2001);
+  rec->set_process_name("com.test.appa");
+  rec->set_start_seq_id(1);
+  rec->set_proc_state(1002);
+  std::string bytes = snapshot.SerializeAsString();
+  tracker_->ParseProcessStateDump(
+      1000, protozero::ConstBytes{
+                reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size()});
+
+  auto opt_upid_a = context_.process_tracker->GetProcessOrNull(2001);
+  ASSERT_TRUE(opt_upid_a.has_value());
+  EXPECT_EQ(context_.storage->GetString(
+                *context_.storage->process_table()[*opt_upid_a].name()),
+            "com.test.appa");
+
+  // Mid-trace (ts = 5000), PID 2001 is recycled to App B (start_seq_id 2).
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot> snapshot_b;
+  auto* rec_b = snapshot_b->add_record();
+  rec_b->set_pid(2001);
+  rec_b->set_process_name("com.test.appb");
+  rec_b->set_start_seq_id(2);
+  rec_b->set_proc_state(1002);
+  std::string bytes_b = snapshot_b.SerializeAsString();
+  tracker_->ParseProcessStateDump(
+      5000,
+      protozero::ConstBytes{reinterpret_cast<const uint8_t*>(bytes_b.data()),
+                            bytes_b.size()});
+
+  auto opt_upid_b = context_.process_tracker->GetProcessOrNull(2001);
+  ASSERT_TRUE(opt_upid_b.has_value());
+  EXPECT_NE(*opt_upid_b, *opt_upid_a);
+  EXPECT_EQ(context_.storage->GetString(
+                *context_.storage->process_table()[*opt_upid_b].name()),
+            "com.test.appb");
+
+  // App A must have end_ts set to 5000.
+  EXPECT_EQ(*context_.storage->process_table()[*opt_upid_a].end_ts(), 5000);
+
+  // Verify process table has 3 entries (reserved swapper upid 0 + App A + App
+  // B).
+  EXPECT_EQ(context_.storage->process_table().row_count(), 3u);
+
+  // Now Finalize() is called at EOF: verify it does NOT corrupt App B or create
+  // a 3rd UPID for PID 2001.
+  tracker_->Finalize();
+
+  EXPECT_EQ(context_.storage->process_table().row_count(), 3u);
+  auto current_upid = context_.process_tracker->GetProcessOrNull(2001);
+  ASSERT_TRUE(current_upid.has_value());
+  EXPECT_EQ(*current_upid, *opt_upid_b);
+}
+
+TEST_F(AndroidProcessStateTrackerTest, ReconcileKernelAuthority) {
+  // Both ftrace and dump_process_metadata enabled -> kKernelAuthority.
+  tracker_->OnConfigDetected(/*ftrace_configured=*/true,
+                             /*dump_process_metadata=*/true);
+
+  protozero::HeapBuffered<fb::AndroidProcessStateSnapshot> snapshot;
+  auto* rec = snapshot->add_record();
+  rec->set_pid(3001);
+  rec->set_uid(10070);
+  rec->set_process_name("com.test.kernel");
+  rec->set_start_seq_id(77);
+  rec->set_proc_state(1002);
+  rec->set_oom_score(200);
+
+  std::string bytes = snapshot.SerializeAsString();
+  tracker_->ParseProcessStateDump(
+      1000, protozero::ConstBytes{
+                reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size()});
+
+  tracker_->Finalize();
+
+  auto opt_upid = context_.process_tracker->GetProcessOrNull(3001);
+  ASSERT_TRUE(opt_upid.has_value());
+  auto prr = context_.storage->process_table()[*opt_upid];
+  EXPECT_EQ(prr.pid(), 3001u);
+  EXPECT_EQ(context_.storage->GetString(*prr.name()), "com.test.kernel");
+  EXPECT_EQ(prr.android_start_seq_id(), 77);
+
+  EXPECT_EQ(process_state_table_->row_count(), 1u);
+  auto state_row = (*process_state_table_)[0];
+  EXPECT_EQ(state_row.upid(), *opt_upid);
+  EXPECT_EQ(state_row.is_initial(), 1u);
+  EXPECT_EQ(state_row.oom_score(), 200);
+}
+
+}  // namespace
+}  // namespace perfetto::trace_processor::android_process_state
diff --git a/src/trace_processor/plugins/android_process_state/tables.py b/src/trace_processor/plugins/android_process_state/tables.py
index a087619..c587aa3 100644
--- a/src/trace_processor/plugins/android_process_state/tables.py
+++ b/src/trace_processor/plugins/android_process_state/tables.py
@@ -35,10 +35,12 @@
           cpp_access=CppAccess.READ,
           cpp_access_duration=CppAccessDuration.POST_FINALIZATION),
         C('upid', CppTableId(PROCESS_TABLE), cpp_access=CppAccess.READ),
-        C('proc_state', CppOptional(CppString())),
-        C('oom_score', CppOptional(CppInt32())),
-        C('capability_flags', CppOptional(CppInt32())),
-        C('reason', CppOptional(CppString())),
+        C('proc_state', CppOptional(CppString()), cpp_access=CppAccess.READ),
+        C('oom_score', CppOptional(CppInt32()), cpp_access=CppAccess.READ),
+        C('capability_flags',
+          CppOptional(CppInt32()),
+          cpp_access=CppAccess.READ),
+        C('reason', CppOptional(CppString()), cpp_access=CppAccess.READ),
         C('is_initial', CppUint32(), cpp_access=CppAccess.READ),
     ],
     tabledoc=TableDoc(
diff --git a/src/trace_processor/tables/metadata_tables.py b/src/trace_processor/tables/metadata_tables.py
index d3b510d..743f18c 100644
--- a/src/trace_processor/tables/metadata_tables.py
+++ b/src/trace_processor/tables/metadata_tables.py
@@ -215,6 +215,12 @@
             cpp_access=CppAccess.READ_AND_HIGH_PERF_WRITE,
         ),
         C(
+            'android_start_seq_id',
+            CppOptional(CppInt64()),
+            sql_access=SqlAccess.HIGH_PERF,
+            cpp_access=CppAccess.READ_AND_HIGH_PERF_WRITE,
+        ),
+        C(
             'cmdline',
             CppOptional(CppString()),
             sql_access=SqlAccess.HIGH_PERF,
@@ -284,6 +290,11 @@
                 Related to Android multi-user (not to be confused with the
                 unix uid)
                 ''',
+            'android_start_seq_id':
+                '''
+                The Android start sequence id of the process, monotonic per app
+                start.
+                ''',
             'cmdline':
                 '/proc/cmdline for this process.',
             'arg_set_id':