android_sdk: Add nested-track ordering and merging

Expose sibling ordering and sibling merging for nested tracks end to
end, from the C SDK high-level ABI through te_macros, the shared lib,
JNI and the Android Java SDK.

- PerfettoTeHlNestedTrackNamed gains sibling_order_rank,
  child_ordering, sibling_merge_behavior and a string/integer sibling
  merge key, with PERFETTO_TE_HL_CHILD_ORDERING_* and
  PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_* constants mirroring the proto
  enums.
- te_macros: PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED(NAME, ID, RANK,
  ORDERING) and PERFETTO_TE_NESTED_TRACK_NAMED_MERGED(NAME, ID,
  BEHAVIOR, KEY_STR, KEY_INT).
- hl.cc: EmitNamedTrack now takes the nested-named struct and emits the
  new fields on the TrackDescriptor.
- Java: PerfettoTrack.setSiblingOrderRank() / setChildOrdering() /
  setSiblingMergeBehavior() / setSiblingMergeKey(long|String) plus
  CHILD_ORDERING_* and SIBLING_MERGE_BEHAVIOR_* constants; the chain
  carries parallel arrays through JNI (direct region copies, lengths
  validated against the names array).

Rust bindings mirror the struct additions. The example, shared-lib
integration tests and Java host tests cover both features.

Change-Id: I447196a0956ab9445b62a21c3bc3488c17e3e170
diff --git a/contrib/rust-sdk/perfetto-sys/src/bindings.rs b/contrib/rust-sdk/perfetto-sys/src/bindings.rs
index d6497ba..3567829 100644
--- a/contrib/rust-sdk/perfetto-sys/src/bindings.rs
+++ b/contrib/rust-sdk/perfetto-sys/src/bindings.rs
@@ -834,6 +834,11 @@
     pub name: *const ::std::os::raw::c_char,
     pub id: u64,
     pub is_name_static: bool,
+    pub sibling_order_rank: i32,
+    pub child_ordering: u32,
+    pub sibling_merge_behavior: u32,
+    pub sibling_merge_key_str: *const ::std::os::raw::c_char,
+    pub sibling_merge_key_int: u64,
 }
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
diff --git a/contrib/rust-sdk/perfetto/src/track_event.rs b/contrib/rust-sdk/perfetto/src/track_event.rs
index bfb60d2..d6d133b 100644
--- a/contrib/rust-sdk/perfetto/src/track_event.rs
+++ b/contrib/rust-sdk/perfetto/src/track_event.rs
@@ -1076,6 +1076,11 @@
                         name: cname.as_ptr(),
                         id: *id,
                         is_name_static: false,
+                        sibling_order_rank: 0,
+                        child_ordering: 0,
+                        sibling_merge_behavior: 0,
+                        sibling_merge_key_str: ptr::null(),
+                        sibling_merge_key_int: 0,
                     },
                     cname,
                 )
diff --git a/examples/shared_lib/example_shlib_track_event.c b/examples/shared_lib/example_shlib_track_event.c
index bdfb3af..ec3569a 100644
--- a/examples/shared_lib/example_shlib_track_event.c
+++ b/examples/shared_lib/example_shlib_track_event.c
@@ -107,6 +107,30 @@
                     PERFETTO_TE_NESTED_TRACK_NAMED("dynamictrack", 2),
                     PERFETTO_TE_NESTED_TRACK_COUNTER("dynamiccounter")),
                 PERFETTO_TE_INT_COUNTER(99));
+    // "Render" orders its children explicitly: "GPU" (rank 1) sorts before
+    // "CPU" (rank 2) regardless of when they first appear.
+    PERFETTO_TE(physics, PERFETTO_TE_INSTANT("gpu_work"),
+                PERFETTO_TE_NESTED_TRACKS(
+                    PERFETTO_TE_NESTED_TRACK_PROCESS(),
+                    PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED(
+                        "Render", 0, 0, PERFETTO_TE_HL_CHILD_ORDERING_EXPLICIT),
+                    PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED("GPU", 0, 1, 0)));
+    PERFETTO_TE(physics, PERFETTO_TE_INSTANT("cpu_work"),
+                PERFETTO_TE_NESTED_TRACKS(
+                    PERFETTO_TE_NESTED_TRACK_PROCESS(),
+                    PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED(
+                        "Render", 0, 0, PERFETTO_TE_HL_CHILD_ORDERING_EXPLICIT),
+                    PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED("CPU", 0, 2, 0)));
+    // "Worker" tracks merge by an explicit key: same-keyed siblings are
+    // displayed as one track.
+    PERFETTO_TE(
+        physics, PERFETTO_TE_INSTANT("pool_work"),
+        PERFETTO_TE_NESTED_TRACKS(
+            PERFETTO_TE_NESTED_TRACK_PROCESS(),
+            PERFETTO_TE_NESTED_TRACK_NAMED_MERGED(
+                "Worker", 0,
+                PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY,
+                "worker_pool", 0)));
     sleep(1);
   }
 }
diff --git a/include/perfetto/public/abi/track_event_hl_abi.h b/include/perfetto/public/abi/track_event_hl_abi.h
index 69dd2cc..1b1dec6 100644
--- a/include/perfetto/public/abi/track_event_hl_abi.h
+++ b/include/perfetto/public/abi/track_event_hl_abi.h
@@ -316,6 +316,20 @@
   uint32_t type;
 };
 
+// How a track orders its direct children. Values mirror
+// TrackDescriptor.ChildTracksOrdering; 0 (unknown) leaves it unset.
+#define PERFETTO_TE_HL_CHILD_ORDERING_UNKNOWN 0
+#define PERFETTO_TE_HL_CHILD_ORDERING_LEXICOGRAPHIC 1
+#define PERFETTO_TE_HL_CHILD_ORDERING_CHRONOLOGICAL 2
+#define PERFETTO_TE_HL_CHILD_ORDERING_EXPLICIT 3
+
+// How analysis tools merge a track with its eligible siblings. Values mirror
+// TrackDescriptor.SiblingMergeBehavior; 0 (unspecified) leaves it unset.
+#define PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_UNSPECIFIED 0
+#define PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_TRACK_NAME 1
+#define PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_NONE 2
+#define PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY 3
+
 // PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED
 struct PerfettoTeHlNestedTrackNamed {
   struct PerfettoTeHlNestedTrack header;
@@ -324,6 +338,26 @@
   uint64_t id;
   // If true, `name` is a compile-time constant and is emitted as static_name.
   bool is_name_static;
+  // This track's rank among its siblings (lower sorts first). Honored only
+  // when the parent's `child_ordering` is EXPLICIT. 0 leaves it unset.
+  int32_t sibling_order_rank;
+  // How this track orders its own children: one of the
+  // PERFETTO_TE_HL_CHILD_ORDERING_* values. 0 (unknown) leaves it unset.
+  uint32_t child_ordering;
+  // How this track is merged with eligible siblings: one of the
+  // PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_* values. 0 (unspecified) leaves it
+  // unset.
+  uint32_t sibling_merge_behavior;
+  // Null terminated string key selecting which siblings this track is merged
+  // with. Only meaningful when `sibling_merge_behavior` is
+  // BY_SIBLING_MERGE_KEY; emitted as the TrackDescriptor `sibling_merge_key`.
+  // NULL leaves it unset, in which case `sibling_merge_key_int` is emitted
+  // instead (the two map onto a protobuf oneof).
+  const char* sibling_merge_key_str;
+  // Integer key selecting which siblings this track is merged with. Only
+  // emitted (as `sibling_merge_key_int`) when `sibling_merge_behavior` is
+  // BY_SIBLING_MERGE_KEY and `sibling_merge_key_str` is NULL.
+  uint64_t sibling_merge_key_int;
 };
 
 struct PerfettoTeHlNestedTrackProto {
diff --git a/include/perfetto/public/te_macros.h b/include/perfetto/public/te_macros.h
index e04632f..5a25b24 100644
--- a/include/perfetto/public/te_macros.h
+++ b/include/perfetto/public/te_macros.h
@@ -397,12 +397,58 @@
 
 // A track called `NAME` (const char *), uniquely identified by `NAME`, `ID` (a
 // uint64_t) and its parent hierarchy.
-#define PERFETTO_TE_NESTED_TRACK_NAMED(NAME, ID) \
-  PERFETTO_REINTERPRET_CAST(                     \
-      struct PerfettoTeHlNestedTrack*,           \
-      PERFETTO_I_TE_COMPOUND_LITERAL_ADDR(       \
-          PerfettoTeHlNestedTrackNamed,          \
-          {{PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED}, NAME, ID, false}))
+#define PERFETTO_TE_NESTED_TRACK_NAMED(NAME, ID)                           \
+  PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlNestedTrack*,               \
+                            PERFETTO_I_TE_COMPOUND_LITERAL_ADDR(           \
+                                PerfettoTeHlNestedTrackNamed,              \
+                                {{PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED}, \
+                                 NAME,                                     \
+                                 ID,                                       \
+                                 false,                                    \
+                                 0,                                        \
+                                 0,                                        \
+                                 0,                                        \
+                                 PERFETTO_NULL,                            \
+                                 0}))
+
+// Like PERFETTO_TE_NESTED_TRACK_NAMED, but also sets this level's
+// `sibling_order_rank` (its rank among siblings; lower sorts first, honored
+// when the parent's ordering is EXPLICIT) and `child_ordering` (how this level
+// orders its own children: a PERFETTO_TE_HL_CHILD_ORDERING_* value).
+#define PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED(NAME, ID, RANK, ORDERING)   \
+  PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlNestedTrack*,               \
+                            PERFETTO_I_TE_COMPOUND_LITERAL_ADDR(           \
+                                PerfettoTeHlNestedTrackNamed,              \
+                                {{PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED}, \
+                                 NAME,                                     \
+                                 ID,                                       \
+                                 false,                                    \
+                                 RANK,                                     \
+                                 ORDERING,                                 \
+                                 0,                                        \
+                                 PERFETTO_NULL,                            \
+                                 0}))
+
+// Like PERFETTO_TE_NESTED_TRACK_NAMED, but also sets how this level is merged
+// with its eligible siblings: `BEHAVIOR` is a
+// PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_* value, `KEY_STR` (a const char*,
+// can be PERFETTO_NULL) and `KEY_INT` (a uint64_t) select the siblings this
+// track is merged with when `BEHAVIOR` is BY_SIBLING_MERGE_KEY (`KEY_STR`, if
+// not NULL, takes precedence over `KEY_INT`).
+#define PERFETTO_TE_NESTED_TRACK_NAMED_MERGED(NAME, ID, BEHAVIOR, KEY_STR, \
+                                              KEY_INT)                     \
+  PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlNestedTrack*,               \
+                            PERFETTO_I_TE_COMPOUND_LITERAL_ADDR(           \
+                                PerfettoTeHlNestedTrackNamed,              \
+                                {{PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED}, \
+                                 NAME,                                     \
+                                 ID,                                       \
+                                 false,                                    \
+                                 0,                                        \
+                                 0,                                        \
+                                 BEHAVIOR,                                 \
+                                 KEY_STR,                                  \
+                                 KEY_INT}))
 
 // A track uniquely identified by `ID` (a uint64_t) and its parent hierarchy.
 // The rest of the params should be PERFETTO_TE_PROTO_FIELD_* macros and should
diff --git a/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrack.java b/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrack.java
index 03d835c..2ebd67b 100644
--- a/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrack.java
+++ b/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrack.java
@@ -40,8 +40,16 @@
  * the C SDK's nested-track behaviour. The track uuid is derived natively exactly
  * as the C SDK derives it. Emit cost grows with the nesting depth.
  *
- * <p>This is the nesting-only shape supported by the high-level ABI; sibling
- * ordering, counter units and similar are intentionally not exposed here.
+ * <p>By default the UI decides how a track's children are arranged. For explicit
+ * control, give the parent {@link #setChildOrdering} {@link
+ * #CHILD_ORDERING_EXPLICIT} and each child a {@link #setSiblingOrderRank} (lower
+ * sorts first).
+ *
+ * <p>By default sibling tracks with the same name are merged when displayed. To
+ * control it, set {@link #setSiblingMergeBehavior} (e.g. {@link
+ * #SIBLING_MERGE_BEHAVIOR_NONE} to never merge, or {@link
+ * #SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY} together with {@link
+ * #setSiblingMergeKey} to merge by an explicit key instead of by name).
  */
 public final class PerfettoTrack {
   // Root scope of the chain. Mirrors RootType in tracing_sdk.h.
@@ -49,13 +57,41 @@
   static final int ROOT_PROCESS = 1;
   static final int ROOT_THREAD = 2;
 
+  // How a track orders its direct children, for {@link #setChildOrdering}.
+  // Values mirror TrackDescriptor.ChildTracksOrdering.
+  public static final int CHILD_ORDERING_UNKNOWN = 0;
+  public static final int CHILD_ORDERING_LEXICOGRAPHIC = 1;
+  public static final int CHILD_ORDERING_CHRONOLOGICAL = 2;
+  public static final int CHILD_ORDERING_EXPLICIT = 3;
+
+  // How a track is merged with its eligible siblings, for
+  // {@link #setSiblingMergeBehavior}. Values mirror
+  // TrackDescriptor.SiblingMergeBehavior.
+  public static final int SIBLING_MERGE_BEHAVIOR_UNSPECIFIED = 0;
+  public static final int SIBLING_MERGE_BEHAVIOR_BY_TRACK_NAME = 1;
+  public static final int SIBLING_MERGE_BEHAVIOR_NONE = 2;
+  public static final int SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY = 3;
+
   // Default per-level id: no disambiguation between same-named sibling tracks.
   private static final long DEFAULT_ID = 0;
 
   final int mRootType;
-  // Names and ids of the chain, outermost (closest to the root) first.
+  // Per-level state of the chain, outermost (closest to the root) first. The
+  // arrays are parallel; each setter clones the array it changes and shares
+  // the rest.
   final String[] mNames;
   final long[] mIds;
+  // This level's rank among its siblings; 0 means unset.
+  final int[] mSiblingOrderRanks;
+  // How this level orders its own children; CHILD_ORDERING_UNKNOWN means unset.
+  final int[] mChildOrderings;
+  // How this level merges with its siblings; SIBLING_MERGE_BEHAVIOR_UNSPECIFIED
+  // means unset.
+  final int[] mSiblingMergeBehaviors;
+  // The per-level merge key, a protobuf oneof: a non-null string key wins over
+  // the integer key.
+  final String[] mSiblingMergeKeyStrs;
+  final long[] mSiblingMergeKeyInts;
 
   // The handle's native nested-tracks extra, built lazily on first use and held
   // for the handle's lifetime. Freed by the cleaner when the handle is collected.
@@ -65,10 +101,36 @@
   // needs no native lib, so it is safe to hold statically.
   private static final PerfettoNativeMemoryCleaner sCleaner = new PerfettoNativeMemoryCleaner();
 
-  private PerfettoTrack(int rootType, String[] names, long[] ids) {
+  private PerfettoTrack(
+      int rootType,
+      String[] names,
+      long[] ids,
+      int[] siblingOrderRanks,
+      int[] childOrderings,
+      int[] siblingMergeBehaviors,
+      String[] siblingMergeKeyStrs,
+      long[] siblingMergeKeyInts) {
     mRootType = rootType;
     mNames = names;
     mIds = ids;
+    mSiblingOrderRanks = siblingOrderRanks;
+    mChildOrderings = childOrderings;
+    mSiblingMergeBehaviors = siblingMergeBehaviors;
+    mSiblingMergeKeyStrs = siblingMergeKeyStrs;
+    mSiblingMergeKeyInts = siblingMergeKeyInts;
+  }
+
+  /** A track named {@code name} rooted at {@code rootType}. */
+  private static PerfettoTrack root(int rootType, String name) {
+    return new PerfettoTrack(
+        rootType,
+        new String[] {name},
+        new long[] {DEFAULT_ID},
+        new int[1],
+        new int[1],
+        new int[1],
+        new String[1],
+        new long[1]);
   }
 
   /**
@@ -83,7 +145,17 @@
   NestedTracks nestedTracks() {
     NestedTracks n = mNested;
     if (n == null) {
-      n = new NestedTracks(mRootType, mNames, mIds, sCleaner);
+      n =
+          new NestedTracks(
+              mRootType,
+              mNames,
+              mIds,
+              mSiblingOrderRanks,
+              mChildOrderings,
+              mSiblingMergeBehaviors,
+              mSiblingMergeKeyStrs,
+              mSiblingMergeKeyInts,
+              sCleaner);
       mNested = n;
     }
     return n;
@@ -91,17 +163,17 @@
 
   /** A track named {@code name} rooted at the process track. */
   public static PerfettoTrack process(@CompileTimeConstant String name) {
-    return new PerfettoTrack(ROOT_PROCESS, new String[] {name}, new long[] {DEFAULT_ID});
+    return root(ROOT_PROCESS, name);
   }
 
   /** A track named {@code name} rooted at the emitting thread's track. */
   public static PerfettoTrack thread(@CompileTimeConstant String name) {
-    return new PerfettoTrack(ROOT_THREAD, new String[] {name}, new long[] {DEFAULT_ID});
+    return root(ROOT_THREAD, name);
   }
 
   /** A track named {@code name} rooted at the global scope. */
   public static PerfettoTrack global(@CompileTimeConstant String name) {
-    return new PerfettoTrack(ROOT_GLOBAL, new String[] {name}, new long[] {DEFAULT_ID});
+    return root(ROOT_GLOBAL, name);
   }
 
   /** A child track named {@code name} nested under this one. */
@@ -117,10 +189,120 @@
     int n = mNames.length;
     String[] names = new String[n + 1];
     long[] ids = new long[n + 1];
+    int[] ranks = new int[n + 1];
+    int[] orderings = new int[n + 1];
+    int[] mergeBehaviors = new int[n + 1];
+    String[] mergeKeyStrs = new String[n + 1];
+    long[] mergeKeyInts = new long[n + 1];
     System.arraycopy(mNames, 0, names, 0, n);
     System.arraycopy(mIds, 0, ids, 0, n);
+    System.arraycopy(mSiblingOrderRanks, 0, ranks, 0, n);
+    System.arraycopy(mChildOrderings, 0, orderings, 0, n);
+    System.arraycopy(mSiblingMergeBehaviors, 0, mergeBehaviors, 0, n);
+    System.arraycopy(mSiblingMergeKeyStrs, 0, mergeKeyStrs, 0, n);
+    System.arraycopy(mSiblingMergeKeyInts, 0, mergeKeyInts, 0, n);
     names[n] = name;
     ids[n] = id;
-    return new PerfettoTrack(mRootType, names, ids);
+    return new PerfettoTrack(
+        mRootType, names, ids, ranks, orderings, mergeBehaviors, mergeKeyStrs, mergeKeyInts);
+  }
+
+  /**
+   * This track's rank among its siblings; lower sorts first. Only honored when
+   * the parent track's {@link #setChildOrdering} is {@link
+   * #CHILD_ORDERING_EXPLICIT}.
+   */
+  public PerfettoTrack setSiblingOrderRank(int rank) {
+    int[] ranks = mSiblingOrderRanks.clone();
+    ranks[ranks.length - 1] = rank;
+    return new PerfettoTrack(
+        mRootType,
+        mNames,
+        mIds,
+        ranks,
+        mChildOrderings,
+        mSiblingMergeBehaviors,
+        mSiblingMergeKeyStrs,
+        mSiblingMergeKeyInts);
+  }
+
+  /**
+   * How this track orders its own children, one of the {@code CHILD_ORDERING_*}
+   * values.
+   */
+  public PerfettoTrack setChildOrdering(int childOrdering) {
+    int[] orderings = mChildOrderings.clone();
+    orderings[orderings.length - 1] = childOrdering;
+    return new PerfettoTrack(
+        mRootType,
+        mNames,
+        mIds,
+        mSiblingOrderRanks,
+        orderings,
+        mSiblingMergeBehaviors,
+        mSiblingMergeKeyStrs,
+        mSiblingMergeKeyInts);
+  }
+
+  /**
+   * How this track is merged with its eligible siblings, one of the {@code
+   * SIBLING_MERGE_BEHAVIOR_*} values.
+   */
+  public PerfettoTrack setSiblingMergeBehavior(int siblingMergeBehavior) {
+    int[] mergeBehaviors = mSiblingMergeBehaviors.clone();
+    mergeBehaviors[mergeBehaviors.length - 1] = siblingMergeBehavior;
+    return new PerfettoTrack(
+        mRootType,
+        mNames,
+        mIds,
+        mSiblingOrderRanks,
+        mChildOrderings,
+        mergeBehaviors,
+        mSiblingMergeKeyStrs,
+        mSiblingMergeKeyInts);
+  }
+
+  /**
+   * The integer key selecting which siblings this track is merged with. Only
+   * meaningful when {@link #setSiblingMergeBehavior} is {@link
+   * #SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY}. Clears any previously set
+   * string key (the two are a protobuf oneof).
+   */
+  public PerfettoTrack setSiblingMergeKey(long key) {
+    String[] mergeKeyStrs = mSiblingMergeKeyStrs.clone();
+    long[] mergeKeyInts = mSiblingMergeKeyInts.clone();
+    mergeKeyStrs[mergeKeyStrs.length - 1] = null;
+    mergeKeyInts[mergeKeyInts.length - 1] = key;
+    return new PerfettoTrack(
+        mRootType,
+        mNames,
+        mIds,
+        mSiblingOrderRanks,
+        mChildOrderings,
+        mSiblingMergeBehaviors,
+        mergeKeyStrs,
+        mergeKeyInts);
+  }
+
+  /**
+   * The string key selecting which siblings this track is merged with. Only
+   * meaningful when {@link #setSiblingMergeBehavior} is {@link
+   * #SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY}. Clears any previously set
+   * integer key (the two are a protobuf oneof).
+   */
+  public PerfettoTrack setSiblingMergeKey(String key) {
+    String[] mergeKeyStrs = mSiblingMergeKeyStrs.clone();
+    long[] mergeKeyInts = mSiblingMergeKeyInts.clone();
+    mergeKeyStrs[mergeKeyStrs.length - 1] = key;
+    mergeKeyInts[mergeKeyInts.length - 1] = 0;
+    return new PerfettoTrack(
+        mRootType,
+        mNames,
+        mIds,
+        mSiblingOrderRanks,
+        mChildOrderings,
+        mSiblingMergeBehaviors,
+        mergeKeyStrs,
+        mergeKeyInts);
   }
 }
diff --git a/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrackEventExtra.java b/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrackEventExtra.java
index 0e83078..f4f055e 100644
--- a/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrackEventExtra.java
+++ b/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrackEventExtra.java
@@ -172,8 +172,22 @@
         int rootType,
         String[] names,
         long[] ids,
+        int[] siblingOrderRanks,
+        int[] childOrderings,
+        int[] siblingMergeBehaviors,
+        String[] siblingMergeKeyStrs,
+        long[] siblingMergeKeyInts,
         PerfettoNativeMemoryCleaner memoryCleaner) {
-      mPtr = native_init(rootType, names, ids);
+      mPtr =
+          native_init(
+              rootType,
+              names,
+              ids,
+              siblingOrderRanks,
+              childOrderings,
+              siblingMergeBehaviors,
+              siblingMergeKeyStrs,
+              siblingMergeKeyInts);
       mExtraPtr = native_get_extra_ptr(mPtr);
       memoryCleaner.registerNativeAllocation(this, mPtr, native_delete());
     }
@@ -184,7 +198,15 @@
     }
 
     @FastNative
-    private static native long native_init(int rootType, String[] names, long[] ids);
+    private static native long native_init(
+        int rootType,
+        String[] names,
+        long[] ids,
+        int[] siblingOrderRanks,
+        int[] childOrderings,
+        int[] siblingMergeBehaviors,
+        String[] siblingMergeKeyStrs,
+        long[] siblingMergeKeyInts);
 
     @CriticalNative
     private static native long native_delete();
diff --git a/src/android_sdk/java/test/dev/perfetto/sdk/test/PerfettoTraceTest.java b/src/android_sdk/java/test/dev/perfetto/sdk/test/PerfettoTraceTest.java
index 0546518..311876b 100644
--- a/src/android_sdk/java/test/dev/perfetto/sdk/test/PerfettoTraceTest.java
+++ b/src/android_sdk/java/test/dev/perfetto/sdk/test/PerfettoTraceTest.java
@@ -345,6 +345,103 @@
   }
 
   @Test
+  public void testSortedNestedTrack() throws Exception {
+    TraceConfig traceConfig = getTraceConfig(FOO);
+
+    PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
+
+    // "render" orders its children explicitly; each child sets its rank.
+    PerfettoTrack render =
+        PerfettoTrack.process("render").setChildOrdering(PerfettoTrack.CHILD_ORDERING_EXPLICIT);
+    PerfettoTrack gpu = render.child("gpu").setSiblingOrderRank(1);
+    PerfettoTrack cpu = render.child("cpu").setSiblingOrderRank(2);
+    PerfettoTrace.instant(FOO_CATEGORY, "gpu_work").usingTrack(gpu).emit();
+    PerfettoTrace.instant(FOO_CATEGORY, "cpu_work").usingTrack(cpu).emit();
+
+    Trace trace = Trace.parseFrom(session.close());
+
+    Map<String, TrackDescriptor> byName = indexDescriptorsByStaticName(trace);
+
+    // The parent declares explicit child ordering.
+    assertThat(byName.get("render").getChildOrdering())
+        .isEqualTo(TrackDescriptor.ChildTracksOrdering.EXPLICIT);
+    // Each child carries its sibling_order_rank.
+    assertThat(byName.get("gpu").getSiblingOrderRank()).isEqualTo(1);
+    assertThat(byName.get("cpu").getSiblingOrderRank()).isEqualTo(2);
+  }
+
+  @Test
+  public void testMergedNestedTrack() throws Exception {
+    TraceConfig traceConfig = getTraceConfig(FOO);
+
+    PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
+
+    PerfettoTrack parent = PerfettoTrack.process("merge_parent");
+    // Merged with same-keyed siblings via a string key.
+    PerfettoTrack strKeyed =
+        parent
+            .child("str_keyed")
+            .setSiblingMergeBehavior(PerfettoTrack.SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY)
+            .setSiblingMergeKey("merge_group_a");
+    // Merged with same-keyed siblings via an integer key.
+    PerfettoTrack intKeyed =
+        parent
+            .child("int_keyed")
+            .setSiblingMergeBehavior(PerfettoTrack.SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY)
+            .setSiblingMergeKey(42);
+    // Never merged with any sibling.
+    PerfettoTrack lone =
+        parent.child("lone").setSiblingMergeBehavior(PerfettoTrack.SIBLING_MERGE_BEHAVIOR_NONE);
+    PerfettoTrack keyWithoutBehavior =
+        parent.child("key_without_behavior").setSiblingMergeKey("x");
+    PerfettoTrace.instant(FOO_CATEGORY, "event_a").usingTrack(strKeyed).emit();
+    PerfettoTrace.instant(FOO_CATEGORY, "event_b").usingTrack(intKeyed).emit();
+    PerfettoTrace.instant(FOO_CATEGORY, "event_c").usingTrack(lone).emit();
+    PerfettoTrace.instant(FOO_CATEGORY, "event_d").usingTrack(keyWithoutBehavior).emit();
+
+    Trace trace = Trace.parseFrom(session.close());
+
+    Map<String, TrackDescriptor> byName = indexDescriptorsByStaticName(trace);
+
+    TrackDescriptor strKeyedTd = byName.get("str_keyed");
+    assertThat(strKeyedTd.getSiblingMergeBehavior())
+        .isEqualTo(TrackDescriptor.SiblingMergeBehavior.SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY);
+    assertThat(strKeyedTd.getSiblingMergeKey()).isEqualTo("merge_group_a");
+    assertThat(strKeyedTd.hasSiblingMergeKeyInt()).isFalse();
+
+    TrackDescriptor intKeyedTd = byName.get("int_keyed");
+    assertThat(intKeyedTd.getSiblingMergeBehavior())
+        .isEqualTo(TrackDescriptor.SiblingMergeBehavior.SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY);
+    assertThat(intKeyedTd.getSiblingMergeKeyInt()).isEqualTo(42);
+    assertThat(intKeyedTd.hasSiblingMergeKey()).isFalse();
+
+    TrackDescriptor loneTd = byName.get("lone");
+    assertThat(loneTd.getSiblingMergeBehavior())
+        .isEqualTo(TrackDescriptor.SiblingMergeBehavior.SIBLING_MERGE_BEHAVIOR_NONE);
+    // No merge key without BY_SIBLING_MERGE_KEY.
+    assertThat(loneTd.hasSiblingMergeKey()).isFalse();
+    assertThat(loneTd.hasSiblingMergeKeyInt()).isFalse();
+
+    TrackDescriptor keyWithoutBehaviorTd = byName.get("key_without_behavior");
+    assertThat(keyWithoutBehaviorTd.hasSiblingMergeKey()).isFalse();
+    assertThat(keyWithoutBehaviorTd.hasSiblingMergeKeyInt()).isFalse();
+  }
+
+  /** Indexes every TrackDescriptor in {@code trace} with a static name by that name. */
+  private static Map<String, TrackDescriptor> indexDescriptorsByStaticName(Trace trace) {
+    Map<String, TrackDescriptor> byName = new HashMap<>();
+    for (TracePacket packet : trace.getPacketList()) {
+      if (packet.hasTrackDescriptor()) {
+        TrackDescriptor td = packet.getTrackDescriptor();
+        if (!td.getStaticName().isEmpty()) {
+          byName.put(td.getStaticName(), td);
+        }
+      }
+    }
+    return byName;
+  }
+
+  @Test
   public void testProcessThreadNamedTrack() throws Exception {
     TraceConfig traceConfig = getTraceConfig(FOO);
 
diff --git a/src/android_sdk/jni/dev_perfetto_sdk_PerfettoTrackEventExtra.cc b/src/android_sdk/jni/dev_perfetto_sdk_PerfettoTrackEventExtra.cc
index d6ba625..4820328 100644
--- a/src/android_sdk/jni/dev_perfetto_sdk_PerfettoTrackEventExtra.cc
+++ b/src/android_sdk/jni/dev_perfetto_sdk_PerfettoTrackEventExtra.cc
@@ -385,29 +385,67 @@
     jclass,
     jint root_type,
     jobjectArray names,
-    jlongArray ids) {
+    jlongArray ids,
+    jintArray sibling_order_ranks,
+    jintArray child_orderings,
+    jintArray sibling_merge_behaviors,
+    jobjectArray merge_keys_str,
+    jlongArray merge_keys_int) {
   const jsize num_names = env->GetArrayLength(names);
-  const jsize num_ids = env->GetArrayLength(ids);
-  // names and ids are built in lockstep by PerfettoTrack, so they should match.
-  // Tracing must never crash the caller, so on a mismatch just log and use the
-  // shorter length rather than over-reading.
-  const jsize n = num_names < num_ids ? num_names : num_ids;
-  if (num_names != num_ids) {
-    __android_log_print(ANDROID_LOG_ERROR, "PerfettoJNI",
-                        "nested track names (%d) and ids (%d) length mismatch",
-                        num_names, num_ids);
+  // All the arrays are built in lockstep by PerfettoTrack, so the lengths
+  // should match. Tracing must never crash the caller, so on a mismatch just
+  // log and use the shortest length rather than over-reading.
+  jsize n = num_names;
+  const jsize lengths[] = {env->GetArrayLength(ids),
+                           env->GetArrayLength(sibling_order_ranks),
+                           env->GetArrayLength(child_orderings),
+                           env->GetArrayLength(sibling_merge_behaviors),
+                           env->GetArrayLength(merge_keys_str),
+                           env->GetArrayLength(merge_keys_int)};
+  for (jsize length : lengths) {
+    if (length != num_names) {
+      __android_log_print(ANDROID_LOG_ERROR, "PerfettoJNI",
+                          "nested track parallel array length mismatch "
+                          "(names %d vs %d)",
+                          num_names, length);
+      n = n < length ? n : length;
+    }
   }
   std::vector<std::string> names_vec;
   names_vec.reserve(static_cast<size_t>(n));
+  std::vector<std::optional<std::string>> merge_keys_str_vec;
+  merge_keys_str_vec.reserve(static_cast<size_t>(n));
   for (jsize i = 0; i < n; i++) {
     jstring s = static_cast<jstring>(env->GetObjectArrayElement(names, i));
     names_vec.emplace_back(StringBuffer::utf16_to_ascii(env, s));
     env->DeleteLocalRef(s);
+    jstring key =
+        static_cast<jstring>(env->GetObjectArrayElement(merge_keys_str, i));
+    if (key == nullptr) {
+      merge_keys_str_vec.emplace_back(std::nullopt);
+    } else {
+      merge_keys_str_vec.emplace_back(StringBuffer::utf16_to_ascii(env, key));
+      env->DeleteLocalRef(key);
+    }
   }
   std::vector<uint64_t> ids_vec(static_cast<size_t>(n));
   env->GetLongArrayRegion(ids, 0, n, reinterpret_cast<jlong*>(ids_vec.data()));
+  std::vector<int32_t> ranks_vec(static_cast<size_t>(n));
+  env->GetIntArrayRegion(sibling_order_ranks, 0, n,
+                         reinterpret_cast<jint*>(ranks_vec.data()));
+  std::vector<uint32_t> orderings_vec(static_cast<size_t>(n));
+  env->GetIntArrayRegion(child_orderings, 0, n,
+                         reinterpret_cast<jint*>(orderings_vec.data()));
+  std::vector<uint32_t> merge_behaviors_vec(static_cast<size_t>(n));
+  env->GetIntArrayRegion(sibling_merge_behaviors, 0, n,
+                         reinterpret_cast<jint*>(merge_behaviors_vec.data()));
+  std::vector<uint64_t> merge_keys_int_vec(static_cast<size_t>(n));
+  env->GetLongArrayRegion(merge_keys_int, 0, n,
+                          reinterpret_cast<jlong*>(merge_keys_int_vec.data()));
   return toJLong(new sdk_for_jni::NestedTracks(
-      static_cast<sdk_for_jni::RootType>(root_type), names_vec, ids_vec));
+      static_cast<sdk_for_jni::RootType>(root_type), names_vec, ids_vec,
+      ranks_vec, orderings_vec, merge_behaviors_vec, merge_keys_str_vec,
+      merge_keys_int_vec));
 }
 
 static jlong dev_perfetto_sdk_PerfettoTrackEventExtraNestedTracks_delete(
@@ -638,7 +676,7 @@
 };
 
 static const JNINativeMethod gNestedTracksMethods[] = {
-    {"native_init", "(I[Ljava/lang/String;[J)J",
+    {"native_init", "(I[Ljava/lang/String;[J[I[I[I[Ljava/lang/String;[J)J",
      (void*)dev_perfetto_sdk_PerfettoTrackEventExtraNestedTracks_init},
     {"native_delete", "()J",
      (void*)dev_perfetto_sdk_PerfettoTrackEventExtraNestedTracks_delete},
diff --git a/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.cc b/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.cc
index d670254..028aae7 100644
--- a/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.cc
+++ b/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.cc
@@ -157,10 +157,16 @@
   delete ptr;
 }
 
-NestedTracks::NestedTracks(RootType root_type,
-                           const std::vector<std::string>& names,
-                           const std::vector<uint64_t>& ids)
-    : names_(names), root_{}, extra_{} {
+NestedTracks::NestedTracks(
+    RootType root_type,
+    const std::vector<std::string>& names,
+    const std::vector<uint64_t>& ids,
+    const std::vector<int32_t>& sibling_order_ranks,
+    const std::vector<uint32_t>& child_orderings,
+    const std::vector<uint32_t>& sibling_merge_behaviors,
+    const std::vector<std::optional<std::string>>& merge_keys_str,
+    const std::vector<uint64_t>& merge_keys_int)
+    : names_(names), merge_keys_str_(merge_keys_str), root_{}, extra_{} {
   const size_t count = names_.size();
   named_.reserve(count);
   ptrs_.reserve(count + 2);
@@ -188,6 +194,12 @@
     entry.name = names_[i].c_str();
     entry.id = ids[i];
     entry.is_name_static = true;
+    entry.sibling_order_rank = sibling_order_ranks[i];
+    entry.child_ordering = child_orderings[i];
+    entry.sibling_merge_behavior = sibling_merge_behaviors[i];
+    entry.sibling_merge_key_str =
+        merge_keys_str_[i] ? merge_keys_str_[i]->c_str() : nullptr;
+    entry.sibling_merge_key_int = merge_keys_int[i];
     named_.push_back(entry);
     ptrs_.push_back(reinterpret_cast<PerfettoTeHlNestedTrack*>(&named_.back()));
   }
diff --git a/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.h b/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.h
index 34547cd..1e07ab9 100644
--- a/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.h
+++ b/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.h
@@ -193,12 +193,22 @@
  * The chain is, outermost first: an optional root (process or thread; global
  * roots have none) followed by one named level per name. The native HL path
  * derives the per-level uuids and emits a descriptor for each level.
+ *
+ * The vectors are parallel, one entry per named level: per-level sibling
+ * order rank, child ordering, sibling merge behavior (values mirror the
+ * PERFETTO_TE_HL_* ABI constants) and sibling merge key. A level's merge key
+ * is the string one if set, the integer one otherwise.
  */
 class NestedTracks {
  public:
   NestedTracks(RootType root_type,
                const std::vector<std::string>& names,
-               const std::vector<uint64_t>& ids);
+               const std::vector<uint64_t>& ids,
+               const std::vector<int32_t>& sibling_order_ranks,
+               const std::vector<uint32_t>& child_orderings,
+               const std::vector<uint32_t>& sibling_merge_behaviors,
+               const std::vector<std::optional<std::string>>& merge_keys_str,
+               const std::vector<uint64_t>& merge_keys_int);
 
   static void delete_track(NestedTracks* track);
 
@@ -206,9 +216,11 @@
 
  private:
   DISALLOW_COPY_AND_ASSIGN(NestedTracks);
-  // ptrs_ point into named_/root_ and named_[i].name into names_; nothing is
-  // resized after construction, so the pointers stay valid.
+  // ptrs_ point into named_/root_, named_[i].name into names_ and
+  // named_[i].sibling_merge_key_str into merge_keys_str_; nothing is resized
+  // after construction, so the pointers stay valid.
   std::vector<std::string> names_;
+  std::vector<std::optional<std::string>> merge_keys_str_;
   PerfettoTeHlNestedTrack root_;
   std::vector<PerfettoTeHlNestedTrackNamed> named_;
   std::vector<PerfettoTeHlNestedTrack*> ptrs_;
diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc
index b82ecd6..cdd6379 100644
--- a/src/shared_lib/test/api_integrationtest.cc
+++ b/src/shared_lib/test/api_integrationtest.cc
@@ -2564,6 +2564,117 @@
   EXPECT_EQ(counter_parent_uuid, registered_track_uuid);
 }
 
+TEST_F(SharedLibTrackEventTest, TrackEventHlNestedTrackOrdered) {
+  TracingSession tracing_session = TracingSession::Builder()
+                                       .set_data_source_name("track_event")
+                                       .add_enabled_category("*")
+                                       .Build();
+
+  PERFETTO_TE(cat1, PERFETTO_TE_INSTANT("event"),
+              PERFETTO_TE_NESTED_TRACKS(
+                  PERFETTO_TE_NESTED_TRACK_PROCESS(),
+                  PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED(
+                      "parent", 0, 0, PERFETTO_TE_HL_CHILD_ORDERING_EXPLICIT),
+                  PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED("child", 0, 7, 0)));
+
+  tracing_session.StopBlocking();
+  std::vector<uint8_t> data = tracing_session.ReadBlocking();
+
+  // The parent track descriptor declares explicit child ordering.
+  EXPECT_THAT(
+      FieldView(data),
+      Contains(PbField(
+          perfetto_protos_Trace_packet_field_number,
+          AllFieldsWithId(
+              perfetto_protos_TracePacket_track_descriptor_field_number,
+              ElementsAre(MsgField(AllOf(
+                  Contains(
+                      PbField(perfetto_protos_TrackDescriptor_name_field_number,
+                              StringField("parent"))),
+                  Contains(PbField(
+                      perfetto_protos_TrackDescriptor_child_ordering_field_number,
+                      VarIntField(
+                          perfetto_protos_TrackDescriptor_EXPLICIT))))))))));
+  // The child track descriptor carries its sibling order rank.
+  EXPECT_THAT(
+      FieldView(data),
+      Contains(PbField(
+          perfetto_protos_Trace_packet_field_number,
+          AllFieldsWithId(
+              perfetto_protos_TracePacket_track_descriptor_field_number,
+              ElementsAre(MsgField(AllOf(
+                  Contains(
+                      PbField(perfetto_protos_TrackDescriptor_name_field_number,
+                              StringField("child"))),
+                  Contains(PbField(
+                      perfetto_protos_TrackDescriptor_sibling_order_rank_field_number,
+                      VarIntField(7))))))))));
+}
+
+TEST_F(SharedLibTrackEventTest, TrackEventHlNestedTrackMerged) {
+  TracingSession tracing_session = TracingSession::Builder()
+                                       .set_data_source_name("track_event")
+                                       .add_enabled_category("*")
+                                       .Build();
+
+  PERFETTO_TE(
+      cat1, PERFETTO_TE_INSTANT("event1"),
+      PERFETTO_TE_NESTED_TRACKS(
+          PERFETTO_TE_NESTED_TRACK_PROCESS(),
+          PERFETTO_TE_NESTED_TRACK_NAMED_MERGED(
+              "str_keyed", 0,
+              PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY,
+              "merge_group_a", 0)));
+  PERFETTO_TE(
+      cat1, PERFETTO_TE_INSTANT("event2"),
+      PERFETTO_TE_NESTED_TRACKS(
+          PERFETTO_TE_NESTED_TRACK_PROCESS(),
+          PERFETTO_TE_NESTED_TRACK_NAMED_MERGED(
+              "int_keyed", 1,
+              PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY,
+              PERFETTO_NULL, 42)));
+
+  tracing_session.StopBlocking();
+  std::vector<uint8_t> data = tracing_session.ReadBlocking();
+
+  // The string-keyed track carries the behavior and the string key.
+  EXPECT_THAT(
+      FieldView(data),
+      Contains(PbField(
+          perfetto_protos_Trace_packet_field_number,
+          AllFieldsWithId(
+              perfetto_protos_TracePacket_track_descriptor_field_number,
+              ElementsAre(MsgField(AllOf(
+                  Contains(
+                      PbField(perfetto_protos_TrackDescriptor_name_field_number,
+                              StringField("str_keyed"))),
+                  Contains(PbField(
+                      perfetto_protos_TrackDescriptor_sibling_merge_behavior_field_number,
+                      VarIntField(
+                          perfetto_protos_TrackDescriptor_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY))),
+                  Contains(PbField(
+                      perfetto_protos_TrackDescriptor_sibling_merge_key_field_number,
+                      StringField("merge_group_a"))))))))));
+  // The integer-keyed track carries the behavior and the integer key.
+  EXPECT_THAT(
+      FieldView(data),
+      Contains(PbField(
+          perfetto_protos_Trace_packet_field_number,
+          AllFieldsWithId(
+              perfetto_protos_TracePacket_track_descriptor_field_number,
+              ElementsAre(MsgField(AllOf(
+                  Contains(
+                      PbField(perfetto_protos_TrackDescriptor_name_field_number,
+                              StringField("int_keyed"))),
+                  Contains(PbField(
+                      perfetto_protos_TrackDescriptor_sibling_merge_behavior_field_number,
+                      VarIntField(
+                          perfetto_protos_TrackDescriptor_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY))),
+                  Contains(PbField(
+                      perfetto_protos_TrackDescriptor_sibling_merge_key_int_field_number,
+                      VarIntField(42))))))))));
+}
+
 TEST_F(SharedLibTrackEventTest, TrackEventIsCategoryEnabled) {
   ASSERT_FALSE(PERFETTO_TE_IS_CATEGORY_ENABLED(cat1));
 
diff --git a/src/shared_lib/track_event/hl.cc b/src/shared_lib/track_event/hl.cc
index 1f19ebb..ce54f5d 100644
--- a/src/shared_lib/track_event/hl.cc
+++ b/src/shared_lib/track_event/hl.cc
@@ -278,15 +278,15 @@
   }
 }
 
+// Emits (once per sequence) the track descriptor for the named track described
+// by `track` (its `header` is ignored) and returns the derived track uuid.
 uint64_t EmitNamedTrack(uint64_t parent_uuid,
-                        const char* name,
-                        uint64_t id,
-                        bool is_name_static,
+                        const PerfettoTeHlNestedTrackNamed& track,
                         perfetto::shlib::TrackEventIncrementalState* incr_state,
                         perfetto::TraceWriterBase* trace_writer) {
   uint64_t uuid = parent_uuid;
-  uuid ^= PerfettoFnv1a(name, strlen(name));
-  uuid ^= id;
+  uuid ^= PerfettoFnv1a(track.name, strlen(track.name));
+  uuid ^= track.id;
   if (incr_state->seen_track_uuids.insert(uuid).second) {
     auto packet = trace_writer->NewTracePacket();
     auto* track_descriptor = packet->set_track_descriptor();
@@ -294,10 +294,33 @@
     if (parent_uuid) {
       track_descriptor->set_parent_uuid(parent_uuid);
     }
-    if (is_name_static) {
-      track_descriptor->set_static_name(name);
+    if (track.is_name_static) {
+      track_descriptor->set_static_name(track.name);
     } else {
-      track_descriptor->set_name(name);
+      track_descriptor->set_name(track.name);
+    }
+    if (track.sibling_order_rank != 0) {
+      track_descriptor->set_sibling_order_rank(track.sibling_order_rank);
+    }
+    if (track.child_ordering != PERFETTO_TE_HL_CHILD_ORDERING_UNKNOWN) {
+      track_descriptor->set_child_ordering(
+          static_cast<protos::pbzero::TrackDescriptor::ChildTracksOrdering>(
+              track.child_ordering));
+    }
+    if (track.sibling_merge_behavior !=
+        PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_UNSPECIFIED) {
+      track_descriptor->set_sibling_merge_behavior(
+          static_cast<protos::pbzero::TrackDescriptor::SiblingMergeBehavior>(
+              track.sibling_merge_behavior));
+    }
+    if (track.sibling_merge_behavior ==
+        PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY) {
+      if (track.sibling_merge_key_str) {
+        track_descriptor->set_sibling_merge_key(track.sibling_merge_key_str);
+      } else {
+        track_descriptor->set_sibling_merge_key_int(
+            track.sibling_merge_key_int);
+      }
     }
   }
   return uuid;
@@ -464,9 +487,12 @@
   } else if (std::holds_alternative<const PerfettoTeHlExtraNamedTrack*>(
                  track)) {
     auto* named_track = std::get<const PerfettoTeHlExtraNamedTrack*>(track);
-    track_uuid = EmitNamedTrack(named_track->parent_uuid, named_track->name,
-                                named_track->id, named_track->is_name_static,
-                                incr_state, trace_writer);
+    PerfettoTeHlNestedTrackNamed named{};
+    named.name = named_track->name;
+    named.id = named_track->id;
+    named.is_name_static = named_track->is_name_static;
+    track_uuid = EmitNamedTrack(named_track->parent_uuid, named, incr_state,
+                                trace_writer);
   } else if (std::holds_alternative<const PerfettoTeHlExtraProtoTrack*>(
                  track)) {
     auto* proto_track = std::get<const PerfettoTeHlExtraProtoTrack*>(track);
@@ -487,9 +513,7 @@
         case PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED: {
           auto* named_track =
               reinterpret_cast<PerfettoTeHlNestedTrackNamed*>(*tp);
-          uuid = EmitNamedTrack(uuid, named_track->name, named_track->id,
-                                named_track->is_name_static, incr_state,
-                                trace_writer);
+          uuid = EmitNamedTrack(uuid, *named_track, incr_state, trace_writer);
         } break;
         case PERFETTO_TE_HL_NESTED_TRACK_TYPE_PROCESS: {
           uuid = perfetto_te_process_track_uuid;