Extend ComplciationData with some experimental types

This patch introduces ListComplicationData which contains a list
of other ComplicationData types, which would typically be
displayed as a table.

This patch also adds the highly experimental
ProtoLayoutComplicationData where proto layout rendering would be
used.

The DelegatingComplicationText equality oveerride now supports
testing equality against PlainComplicationText,
TimeDifferenceComplicationText and TimeFormatComplicationText
which makes it easier to write tests.

Bug: 227722164
Test: Full e2e manual testing with providers and custom renderers
Relnote: We have added two new experimental types of ComplicationData: ListComplicationData & ProtoLayoutComplicationData. Currently there's no rendering support for either of these types and wearos doesn't currently recognize these types if added to a ComplicationDataSource's manifest.
Change-Id: I1811ca12e288f6baa3b5575dbd765b7e23143c0a
diff --git a/wear/watchface/watchface-complications-data/api/public_plus_experimental_1.1.0-beta01.txt b/wear/watchface/watchface-complications-data/api/public_plus_experimental_1.1.0-beta01.txt
index cb5f04a..ffbb413 100644
--- a/wear/watchface/watchface-complications-data/api/public_plus_experimental_1.1.0-beta01.txt
+++ b/wear/watchface/watchface-complications-data/api/public_plus_experimental_1.1.0-beta01.txt
@@ -17,6 +17,9 @@
     property public final androidx.wear.watchface.complications.data.TimeRange validTimeRange;
   }
 
+  @kotlin.RequiresOptIn(message="This is an experimental API that may change or be removed without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ComplicationExperimental {
+  }
+
   public interface ComplicationText {
     method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant);
     method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant);
@@ -32,12 +35,14 @@
 
   public enum ComplicationType {
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType EMPTY;
+    enum_constant @androidx.wear.watchface.complications.data.ComplicationExperimental public static final androidx.wear.watchface.complications.data.ComplicationType LIST;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType LONG_TEXT;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType MONOCHROMATIC_IMAGE;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NOT_CONFIGURED;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_DATA;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_PERMISSION;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType PHOTO_IMAGE;
+    enum_constant @androidx.wear.watchface.complications.data.ComplicationExperimental public static final androidx.wear.watchface.complications.data.ComplicationType PROTO_LAYOUT;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType RANGED_VALUE;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SHORT_TEXT;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SMALL_IMAGE;
@@ -66,6 +71,30 @@
   public final class ImageKt {
   }
 
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public final class ListComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
+    method public java.util.List<androidx.wear.watchface.complications.data.ComplicationData> getComplicationList();
+    method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
+    method public androidx.wear.watchface.complications.data.ListComplicationData.StyleHint getStyleHint();
+    property public final java.util.List<androidx.wear.watchface.complications.data.ComplicationData> complicationList;
+    property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
+    property public final androidx.wear.watchface.complications.data.ListComplicationData.StyleHint styleHint;
+    field public static final int MAX_ITEMS = 5; // 0x5
+    field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
+  }
+
+  public static final class ListComplicationData.Builder {
+    ctor public ListComplicationData.Builder(java.util.List<? extends androidx.wear.watchface.complications.data.ComplicationData> complicationList, androidx.wear.watchface.complications.data.ListComplicationData.StyleHint styleHint, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    method public androidx.wear.watchface.complications.data.ListComplicationData build();
+    method public androidx.wear.watchface.complications.data.ListComplicationData.Builder setDataSource(android.content.ComponentName? dataSource);
+    method public androidx.wear.watchface.complications.data.ListComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction);
+    method public androidx.wear.watchface.complications.data.ListComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange);
+  }
+
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public enum ListComplicationData.StyleHint {
+    enum_constant public static final androidx.wear.watchface.complications.data.ListComplicationData.StyleHint ColumnOfRows;
+    enum_constant public static final androidx.wear.watchface.complications.data.ListComplicationData.StyleHint RowOfColumns;
+  }
+
   public final class LongTextComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
     method public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage();
@@ -184,6 +213,27 @@
     method public androidx.wear.watchface.complications.data.PlainComplicationText build();
   }
 
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public final class ProtoLayoutComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
+    method public androidx.wear.tiles.LayoutElementBuilders.Layout getAmbientLayout();
+    method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
+    method public androidx.wear.tiles.LayoutElementBuilders.Layout getInteractiveLayout();
+    method public androidx.wear.tiles.ResourceBuilders.Resources getLayoutResources();
+    property public final androidx.wear.tiles.LayoutElementBuilders.Layout ambientLayout;
+    property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
+    property public final androidx.wear.tiles.LayoutElementBuilders.Layout interactiveLayout;
+    property public final androidx.wear.tiles.ResourceBuilders.Resources layoutResources;
+    field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
+  }
+
+  public static final class ProtoLayoutComplicationData.Builder {
+    ctor public ProtoLayoutComplicationData.Builder(byte[] ambientLayout, byte[] interactiveLayout, byte[] layoutResources, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    ctor public ProtoLayoutComplicationData.Builder(androidx.wear.tiles.LayoutElementBuilders.Layout ambientLayout, androidx.wear.tiles.LayoutElementBuilders.Layout interactiveLayout, androidx.wear.tiles.ResourceBuilders.Resources resources, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData build();
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData.Builder setDataSource(android.content.ComponentName? dataSource);
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction);
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange);
+  }
+
   public final class RangedValueComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
     method public float getMax();
diff --git a/wear/watchface/watchface-complications-data/api/public_plus_experimental_1.1.0-beta02.txt b/wear/watchface/watchface-complications-data/api/public_plus_experimental_1.1.0-beta02.txt
index cb5f04a..ffbb413 100644
--- a/wear/watchface/watchface-complications-data/api/public_plus_experimental_1.1.0-beta02.txt
+++ b/wear/watchface/watchface-complications-data/api/public_plus_experimental_1.1.0-beta02.txt
@@ -17,6 +17,9 @@
     property public final androidx.wear.watchface.complications.data.TimeRange validTimeRange;
   }
 
+  @kotlin.RequiresOptIn(message="This is an experimental API that may change or be removed without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ComplicationExperimental {
+  }
+
   public interface ComplicationText {
     method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant);
     method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant);
@@ -32,12 +35,14 @@
 
   public enum ComplicationType {
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType EMPTY;
+    enum_constant @androidx.wear.watchface.complications.data.ComplicationExperimental public static final androidx.wear.watchface.complications.data.ComplicationType LIST;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType LONG_TEXT;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType MONOCHROMATIC_IMAGE;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NOT_CONFIGURED;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_DATA;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_PERMISSION;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType PHOTO_IMAGE;
+    enum_constant @androidx.wear.watchface.complications.data.ComplicationExperimental public static final androidx.wear.watchface.complications.data.ComplicationType PROTO_LAYOUT;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType RANGED_VALUE;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SHORT_TEXT;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SMALL_IMAGE;
@@ -66,6 +71,30 @@
   public final class ImageKt {
   }
 
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public final class ListComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
+    method public java.util.List<androidx.wear.watchface.complications.data.ComplicationData> getComplicationList();
+    method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
+    method public androidx.wear.watchface.complications.data.ListComplicationData.StyleHint getStyleHint();
+    property public final java.util.List<androidx.wear.watchface.complications.data.ComplicationData> complicationList;
+    property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
+    property public final androidx.wear.watchface.complications.data.ListComplicationData.StyleHint styleHint;
+    field public static final int MAX_ITEMS = 5; // 0x5
+    field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
+  }
+
+  public static final class ListComplicationData.Builder {
+    ctor public ListComplicationData.Builder(java.util.List<? extends androidx.wear.watchface.complications.data.ComplicationData> complicationList, androidx.wear.watchface.complications.data.ListComplicationData.StyleHint styleHint, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    method public androidx.wear.watchface.complications.data.ListComplicationData build();
+    method public androidx.wear.watchface.complications.data.ListComplicationData.Builder setDataSource(android.content.ComponentName? dataSource);
+    method public androidx.wear.watchface.complications.data.ListComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction);
+    method public androidx.wear.watchface.complications.data.ListComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange);
+  }
+
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public enum ListComplicationData.StyleHint {
+    enum_constant public static final androidx.wear.watchface.complications.data.ListComplicationData.StyleHint ColumnOfRows;
+    enum_constant public static final androidx.wear.watchface.complications.data.ListComplicationData.StyleHint RowOfColumns;
+  }
+
   public final class LongTextComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
     method public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage();
@@ -184,6 +213,27 @@
     method public androidx.wear.watchface.complications.data.PlainComplicationText build();
   }
 
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public final class ProtoLayoutComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
+    method public androidx.wear.tiles.LayoutElementBuilders.Layout getAmbientLayout();
+    method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
+    method public androidx.wear.tiles.LayoutElementBuilders.Layout getInteractiveLayout();
+    method public androidx.wear.tiles.ResourceBuilders.Resources getLayoutResources();
+    property public final androidx.wear.tiles.LayoutElementBuilders.Layout ambientLayout;
+    property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
+    property public final androidx.wear.tiles.LayoutElementBuilders.Layout interactiveLayout;
+    property public final androidx.wear.tiles.ResourceBuilders.Resources layoutResources;
+    field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
+  }
+
+  public static final class ProtoLayoutComplicationData.Builder {
+    ctor public ProtoLayoutComplicationData.Builder(byte[] ambientLayout, byte[] interactiveLayout, byte[] layoutResources, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    ctor public ProtoLayoutComplicationData.Builder(androidx.wear.tiles.LayoutElementBuilders.Layout ambientLayout, androidx.wear.tiles.LayoutElementBuilders.Layout interactiveLayout, androidx.wear.tiles.ResourceBuilders.Resources resources, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData build();
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData.Builder setDataSource(android.content.ComponentName? dataSource);
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction);
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange);
+  }
+
   public final class RangedValueComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
     method public float getMax();
diff --git a/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt b/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
index cb5f04a..ffbb413 100644
--- a/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
@@ -17,6 +17,9 @@
     property public final androidx.wear.watchface.complications.data.TimeRange validTimeRange;
   }
 
+  @kotlin.RequiresOptIn(message="This is an experimental API that may change or be removed without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ComplicationExperimental {
+  }
+
   public interface ComplicationText {
     method public java.time.Instant getNextChangeTime(java.time.Instant afterInstant);
     method public CharSequence getTextAt(android.content.res.Resources resources, java.time.Instant instant);
@@ -32,12 +35,14 @@
 
   public enum ComplicationType {
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType EMPTY;
+    enum_constant @androidx.wear.watchface.complications.data.ComplicationExperimental public static final androidx.wear.watchface.complications.data.ComplicationType LIST;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType LONG_TEXT;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType MONOCHROMATIC_IMAGE;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NOT_CONFIGURED;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_DATA;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType NO_PERMISSION;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType PHOTO_IMAGE;
+    enum_constant @androidx.wear.watchface.complications.data.ComplicationExperimental public static final androidx.wear.watchface.complications.data.ComplicationType PROTO_LAYOUT;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType RANGED_VALUE;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SHORT_TEXT;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType SMALL_IMAGE;
@@ -66,6 +71,30 @@
   public final class ImageKt {
   }
 
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public final class ListComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
+    method public java.util.List<androidx.wear.watchface.complications.data.ComplicationData> getComplicationList();
+    method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
+    method public androidx.wear.watchface.complications.data.ListComplicationData.StyleHint getStyleHint();
+    property public final java.util.List<androidx.wear.watchface.complications.data.ComplicationData> complicationList;
+    property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
+    property public final androidx.wear.watchface.complications.data.ListComplicationData.StyleHint styleHint;
+    field public static final int MAX_ITEMS = 5; // 0x5
+    field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
+  }
+
+  public static final class ListComplicationData.Builder {
+    ctor public ListComplicationData.Builder(java.util.List<? extends androidx.wear.watchface.complications.data.ComplicationData> complicationList, androidx.wear.watchface.complications.data.ListComplicationData.StyleHint styleHint, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    method public androidx.wear.watchface.complications.data.ListComplicationData build();
+    method public androidx.wear.watchface.complications.data.ListComplicationData.Builder setDataSource(android.content.ComponentName? dataSource);
+    method public androidx.wear.watchface.complications.data.ListComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction);
+    method public androidx.wear.watchface.complications.data.ListComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange);
+  }
+
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public enum ListComplicationData.StyleHint {
+    enum_constant public static final androidx.wear.watchface.complications.data.ListComplicationData.StyleHint ColumnOfRows;
+    enum_constant public static final androidx.wear.watchface.complications.data.ListComplicationData.StyleHint RowOfColumns;
+  }
+
   public final class LongTextComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
     method public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage();
@@ -184,6 +213,27 @@
     method public androidx.wear.watchface.complications.data.PlainComplicationText build();
   }
 
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public final class ProtoLayoutComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
+    method public androidx.wear.tiles.LayoutElementBuilders.Layout getAmbientLayout();
+    method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
+    method public androidx.wear.tiles.LayoutElementBuilders.Layout getInteractiveLayout();
+    method public androidx.wear.tiles.ResourceBuilders.Resources getLayoutResources();
+    property public final androidx.wear.tiles.LayoutElementBuilders.Layout ambientLayout;
+    property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
+    property public final androidx.wear.tiles.LayoutElementBuilders.Layout interactiveLayout;
+    property public final androidx.wear.tiles.ResourceBuilders.Resources layoutResources;
+    field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
+  }
+
+  public static final class ProtoLayoutComplicationData.Builder {
+    ctor public ProtoLayoutComplicationData.Builder(byte[] ambientLayout, byte[] interactiveLayout, byte[] layoutResources, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    ctor public ProtoLayoutComplicationData.Builder(androidx.wear.tiles.LayoutElementBuilders.Layout ambientLayout, androidx.wear.tiles.LayoutElementBuilders.Layout interactiveLayout, androidx.wear.tiles.ResourceBuilders.Resources resources, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData build();
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData.Builder setDataSource(android.content.ComponentName? dataSource);
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction);
+    method public androidx.wear.watchface.complications.data.ProtoLayoutComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange);
+  }
+
   public final class RangedValueComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
     method public float getMax();
diff --git a/wear/watchface/watchface-complications-data/build.gradle b/wear/watchface/watchface-complications-data/build.gradle
index 305e615..874613c 100644
--- a/wear/watchface/watchface-complications-data/build.gradle
+++ b/wear/watchface/watchface-complications-data/build.gradle
@@ -17,6 +17,7 @@
 import androidx.build.RunApiTasks
 
 import androidx.build.Publish
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -32,6 +33,8 @@
     implementation("androidx.core:core:1.1.0")
     implementation("androidx.preference:preference:1.1.0")
     implementation("androidx.annotation:annotation:1.2.0")
+    implementation("androidx.wear.tiles:tiles:1.0.0")
+    implementation("androidx.wear.tiles:tiles-proto:1.0.0")
     testImplementation(libs.testCore)
     testImplementation(libs.testRunner)
     testImplementation(libs.testRules)
@@ -43,6 +46,13 @@
     annotationProcessor(project(":versionedparcelable:versionedparcelable-compiler"))
 }
 
+// Allow usage of Kotlin's @OptIn.
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions {
+        freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn"]
+    }
+}
+
 android {
     buildFeatures {
         aidl = true
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
index f4daaec..9f71ee1 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
@@ -81,7 +81,9 @@
             TYPE_SMALL_IMAGE,
             TYPE_LARGE_IMAGE,
             TYPE_NO_PERMISSION,
-            TYPE_NO_DATA
+            TYPE_NO_DATA,
+            TYPE_PROTO_LAYOUT,
+            TYPE_LIST
     })
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @Retention(RetentionPolicy.SOURCE)
@@ -217,6 +219,12 @@
      */
     public static final int TYPE_NO_PERMISSION = 9;
 
+    /** Type that specifies a proto layout based complication. */
+    public static final int TYPE_PROTO_LAYOUT = 11;
+
+    /** Type that specifies a list of complication values. E.g. to support linear 3. */
+    public static final int TYPE_LIST = 12;
+
     /** @hide */
     @IntDef({IMAGE_STYLE_PHOTO, IMAGE_STYLE_ICON})
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -241,31 +249,38 @@
      */
     public static final int IMAGE_STYLE_ICON = 2;
 
-    private static final String FIELD_START_TIME = "START_TIME";
+    // Originally it was planned to support both content and image content descriptions.
+    private static final String FIELD_DATA_SOURCE = "FIELD_DATA_SOURCE";
     private static final String FIELD_END_TIME = "END_TIME";
-    private static final String FIELD_SHORT_TITLE = "SHORT_TITLE";
-    private static final String FIELD_SHORT_TEXT = "SHORT_TEXT";
-    private static final String FIELD_LONG_TITLE = "LONG_TITLE";
-    private static final String FIELD_LONG_TEXT = "LONG_TEXT";
-    private static final String FIELD_VALUE = "VALUE";
-    private static final String FIELD_MIN_VALUE = "MIN_VALUE";
-    private static final String FIELD_MAX_VALUE = "MAX_VALUE";
     private static final String FIELD_ICON = "ICON";
     private static final String FIELD_ICON_BURN_IN_PROTECTION = "ICON_BURN_IN_PROTECTION";
+    private static final String FIELD_IMAGE_STYLE = "IMAGE_STYLE";
+    private static final String FIELD_LARGE_IMAGE = "LARGE_IMAGE";
+    private static final String FIELD_LIST_ENTRIES = "LIST_ENTRIES";
+    private static final String FIELD_LIST_ENTRY_TYPE = "LIST_ENTRY_TYPE";
+    private static final String FIELD_LIST_STYLE_HINT = "LIST_STYLE_HINT";
+    private static final String FIELD_LONG_TITLE = "LONG_TITLE";
+    private static final String FIELD_LONG_TEXT = "LONG_TEXT";
+    private static final String FIELD_MAX_VALUE = "MAX_VALUE";
+    private static final String FIELD_MIN_VALUE = "MIN_VALUE";
+    private static final String FIELD_PLACEHOLDER_FIELDS = "PLACEHOLDER_FIELDS";
+    private static final String FIELD_PLACEHOLDER_TYPE = "PLACEHOLDER_TYPE";
+    private static final String FIELD_PROTO_LAYOUT_AMBIENT = "FIELD_PROTO_LAYOUT_AMBIENT";
+    private static final String FIELD_PROTO_LAYOUT_INTERACTIVE = "FIELD_PROTO_LAYOUT_INTERACTIVE";
+    private static final String FIELD_PROTO_LAYOUT_RESOURCES = "FIELD_PROTO_LAYOUT_RESOURCES";
     private static final String FIELD_SMALL_IMAGE = "SMALL_IMAGE";
     private static final String FIELD_SMALL_IMAGE_BURN_IN_PROTECTION =
             "SMALL_IMAGE_BURN_IN_PROTECTION";
-    private static final String FIELD_LARGE_IMAGE = "LARGE_IMAGE";
+    private static final String FIELD_SHORT_TITLE = "SHORT_TITLE";
+    private static final String FIELD_SHORT_TEXT = "SHORT_TEXT";
+    private static final String FIELD_START_TIME = "START_TIME";
     private static final String FIELD_TAP_ACTION = "TAP_ACTION";
     private static final String FIELD_TAP_ACTION_LOST = "FIELD_TAP_ACTION_LOST";
-    private static final String FIELD_IMAGE_STYLE = "IMAGE_STYLE";
     private static final String FIELD_TIMELINE_START_TIME = "TIMELINE_START_TIME";
     private static final String FIELD_TIMELINE_END_TIME = "TIMELINE_END_TIME";
     private static final String FIELD_TIMELINE_ENTRIES = "TIMELINE";
     private static final String FIELD_TIMELINE_ENTRY_TYPE = "TIMELINE_ENTRY_TYPE";
-    private static final String FIELD_PLACEHOLDER_FIELDS = "PLACEHOLDER_FIELDS";
-    private static final String FIELD_PLACEHOLDER_TYPE = "PLACEHOLDER_TYPE";
-    private static final String FIELD_DATA_SOURCE = "FIELD_DATA_SOURCE";
+    private static final String FIELD_VALUE = "VALUE";
 
     // Originally it was planned to support both content and image content descriptions.
     private static final String FIELD_CONTENT_DESCRIPTION = "IMAGE_CONTENT_DESCRIPTION";
@@ -284,6 +299,12 @@
             {FIELD_LARGE_IMAGE}, // LARGE_IMAGE
             {}, // TYPE_NO_PERMISSION
             {}, // TYPE_NO_DATA
+            { // TYPE_PROTO_LAYOUT
+                    FIELD_PROTO_LAYOUT_AMBIENT,
+                    FIELD_PROTO_LAYOUT_INTERACTIVE,
+                    FIELD_PROTO_LAYOUT_RESOURCES
+            },
+            {FIELD_LIST_ENTRIES} // TYPE_LIST
     };
 
     // Used for validation. OPTIONAL_FIELDS[i] is an array containing all the fields which are
@@ -362,6 +383,15 @@
                     FIELD_TAP_ACTION,
                     FIELD_VALUE,
                     FIELD_DATA_SOURCE
+            },
+            { // TYPE_PROTO_LAYOUT
+                    FIELD_TAP_ACTION, FIELD_CONTENT_DESCRIPTION, FIELD_DATA_SOURCE
+            },
+            { // TYPE_LIST
+                    FIELD_TAP_ACTION,
+                    FIELD_LIST_STYLE_HINT,
+                    FIELD_CONTENT_DESCRIPTION,
+                    FIELD_DATA_SOURCE
             }
     };
 
@@ -383,8 +413,8 @@
             };
 
     @ComplicationType
-    private final int mType;
-    private final Bundle mFields;
+    final int mType;
+    final Bundle mFields;
 
     ComplicationData(@NonNull Builder builder) {
         mType = builder.mType;
@@ -404,7 +434,7 @@
 
     @RequiresApi(api = Build.VERSION_CODES.P)
     private static class SerializedForm implements Serializable {
-        private static final int VERSION_NUMBER = 6;
+        private static final int VERSION_NUMBER = 7;
 
         @NonNull
         ComplicationData mComplicationData;
@@ -446,7 +476,6 @@
             }
             if (isFieldValidForType(FIELD_SMALL_IMAGE, type)) {
                 oos.writeObject(IconSerializableHelper.create(mComplicationData.getSmallImage()));
-
             }
             if (isFieldValidForType(FIELD_SMALL_IMAGE_BURN_IN_PROTECTION, type)) {
                 oos.writeObject(IconSerializableHelper.create(
@@ -473,6 +502,37 @@
             if (isFieldValidForType(FIELD_END_TIME, type)) {
                 oos.writeLong(mComplicationData.getEndDateTimeMillis());
             }
+            oos.writeInt(mComplicationData.mFields.getInt(FIELD_LIST_ENTRY_TYPE));
+            if (isFieldValidForType(FIELD_LIST_STYLE_HINT, type)) {
+                oos.writeInt(mComplicationData.getListStyleHint());
+            }
+            if (isFieldValidForType(FIELD_PROTO_LAYOUT_INTERACTIVE, type)) {
+                byte[] bytes = mComplicationData.getInteractiveLayout();
+                if (bytes == null) {
+                    oos.writeInt(0);
+                } else {
+                    oos.writeInt(bytes.length);
+                    oos.write(bytes);
+                }
+            }
+            if (isFieldValidForType(FIELD_PROTO_LAYOUT_AMBIENT, type)) {
+                byte[] bytes = mComplicationData.getAmbientLayout();
+                if (bytes == null) {
+                    oos.writeInt(0);
+                } else {
+                    oos.writeInt(bytes.length);
+                    oos.write(bytes);
+                }
+            }
+            if (isFieldValidForType(FIELD_PROTO_LAYOUT_RESOURCES, type)) {
+                byte[] bytes = mComplicationData.getLayoutResources();
+                if (bytes == null) {
+                    oos.writeInt(0);
+                } else {
+                    oos.writeInt(bytes.length);
+                    oos.write(bytes);
+                }
+            }
             if (isFieldValidForType(FIELD_DATA_SOURCE, type)) {
                 ComponentName componentName = mComplicationData.getDataSource();
                 if (componentName == null) {
@@ -490,6 +550,15 @@
             long end = mComplicationData.mFields.getLong(FIELD_TIMELINE_END_TIME, -1);
             oos.writeLong(end);
 
+            List<ComplicationData> listEntries = mComplicationData.getListEntries();
+            int listEntriesLength = (listEntries != null) ? listEntries.size() : 0;
+            oos.writeInt(listEntriesLength);
+            if (listEntries != null) {
+                for (ComplicationData data : listEntries) {
+                    new SerializedForm(data).writeObject(oos);
+                }
+            }
+
             if (isFieldValidForType(FIELD_PLACEHOLDER_FIELDS, type)) {
                 ComplicationData placeholder = mComplicationData.getPlaceholder();
                 if (placeholder == null) {
@@ -511,6 +580,12 @@
             }
         }
 
+        private static void putIfNotNull(Bundle fields, String field, Parcelable value) {
+            if (value != null) {
+                fields.putParcelable(field, value);
+            }
+        }
+
         @SuppressLint("SyntheticAccessor") // For mComplicationData.mFields
         private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
             int versionNumber = ois.readInt();
@@ -522,34 +597,34 @@
             Bundle fields = new Bundle();
 
             if (isFieldValidForType(FIELD_LONG_TEXT, type)) {
-                fields.putParcelable(FIELD_LONG_TEXT, (ComplicationText) ois.readObject());
+                putIfNotNull(fields, FIELD_LONG_TEXT, (ComplicationText) ois.readObject());
             }
             if (isFieldValidForType(FIELD_LONG_TITLE, type)) {
-                fields.putParcelable(FIELD_LONG_TITLE, (ComplicationText) ois.readObject());
+                putIfNotNull(fields, FIELD_LONG_TITLE, (ComplicationText) ois.readObject());
             }
             if (isFieldValidForType(FIELD_SHORT_TEXT, type)) {
-                fields.putParcelable(FIELD_SHORT_TEXT, (ComplicationText) ois.readObject());
+                putIfNotNull(fields, FIELD_SHORT_TEXT, (ComplicationText) ois.readObject());
             }
             if (isFieldValidForType(FIELD_SHORT_TITLE, type)) {
-                fields.putParcelable(FIELD_SHORT_TITLE, (ComplicationText) ois.readObject());
+                putIfNotNull(fields, FIELD_SHORT_TITLE, (ComplicationText) ois.readObject());
             }
             if (isFieldValidForType(FIELD_CONTENT_DESCRIPTION, type)) {
-                fields.putParcelable(FIELD_CONTENT_DESCRIPTION,
+                putIfNotNull(fields, FIELD_CONTENT_DESCRIPTION,
                         (ComplicationText) ois.readObject());
             }
             if (isFieldValidForType(FIELD_ICON, type)) {
-                fields.putParcelable(FIELD_ICON, IconSerializableHelper.read(ois));
+                putIfNotNull(fields, FIELD_ICON, IconSerializableHelper.read(ois));
             }
             if (isFieldValidForType(FIELD_ICON_BURN_IN_PROTECTION, type)) {
-                fields.putParcelable(FIELD_ICON_BURN_IN_PROTECTION,
+                putIfNotNull(fields, FIELD_ICON_BURN_IN_PROTECTION,
                         IconSerializableHelper.read(ois));
             }
             if (isFieldValidForType(FIELD_SMALL_IMAGE, type)) {
-                fields.putParcelable(FIELD_SMALL_IMAGE, IconSerializableHelper.read(ois));
+                putIfNotNull(fields, FIELD_SMALL_IMAGE, IconSerializableHelper.read(ois));
             }
             if (isFieldValidForType(FIELD_SMALL_IMAGE_BURN_IN_PROTECTION, type)) {
-                fields.putParcelable(FIELD_SMALL_IMAGE_BURN_IN_PROTECTION,
-                        IconSerializableHelper.read(ois));
+                putIfNotNull(fields,
+                        FIELD_SMALL_IMAGE_BURN_IN_PROTECTION, IconSerializableHelper.read(ois));
             }
             if (isFieldValidForType(FIELD_IMAGE_STYLE, type)) {
                 fields.putInt(FIELD_IMAGE_STYLE, ois.readInt());
@@ -572,6 +647,37 @@
             if (isFieldValidForType(FIELD_END_TIME, type)) {
                 fields.putLong(FIELD_END_TIME, ois.readLong());
             }
+            int listEntryType = ois.readInt();
+            if (listEntryType != 0) {
+                fields.putInt(FIELD_LIST_ENTRY_TYPE, listEntryType);
+            }
+            if (isFieldValidForType(FIELD_LIST_STYLE_HINT, type)) {
+                fields.putInt(FIELD_LIST_STYLE_HINT, ois.readInt());
+            }
+            if (isFieldValidForType(FIELD_PROTO_LAYOUT_INTERACTIVE, type)) {
+                int length = ois.readInt();
+                if (length > 0) {
+                    byte[] protoLayout = new byte[length];
+                    ois.readFully(protoLayout);
+                    fields.putByteArray(FIELD_PROTO_LAYOUT_INTERACTIVE, protoLayout);
+                }
+            }
+            if (isFieldValidForType(FIELD_PROTO_LAYOUT_AMBIENT, type)) {
+                int length = ois.readInt();
+                if (length > 0) {
+                    byte[] ambientProtoLayout = new byte[length];
+                    ois.readFully(ambientProtoLayout);
+                    fields.putByteArray(FIELD_PROTO_LAYOUT_AMBIENT, ambientProtoLayout);
+                }
+            }
+            if (isFieldValidForType(FIELD_PROTO_LAYOUT_RESOURCES, type)) {
+                int length = ois.readInt();
+                if (length > 0) {
+                    byte[] protoLayoutResources = new byte[length];
+                    ois.readFully(protoLayoutResources);
+                    fields.putByteArray(FIELD_PROTO_LAYOUT_RESOURCES, protoLayoutResources);
+                }
+            }
             if (isFieldValidForType(FIELD_DATA_SOURCE, type)) {
                 String componentName = ois.readUTF();
                 if (componentName.isEmpty()) {
@@ -593,6 +699,17 @@
                 fields.putLong(FIELD_TIMELINE_END_TIME, end);
             }
 
+            int listEntriesLength = ois.readInt();
+            if (listEntriesLength != 0) {
+                Parcelable[] parcels = new Parcelable[listEntriesLength];
+                for (int i = 0; i < listEntriesLength; i++) {
+                    SerializedForm entry = new SerializedForm();
+                    entry.readObject(ois);
+                    parcels[i] = entry.mComplicationData.mFields;
+                }
+                fields.putParcelableArray(FIELD_LIST_ENTRIES, parcels);
+            }
+
             if (isFieldValidForType(FIELD_PLACEHOLDER_FIELDS, type)) {
                 if (ois.readBoolean()) {
                     SerializedForm serializedPlaceholder = new SerializedForm();
@@ -765,6 +882,23 @@
         }
     }
 
+    /** Returns the list of {@link ComplicationData} entries for a ListComplicationData. */
+    @Nullable
+    @SuppressWarnings("deprecation")
+    public List<ComplicationData> getListEntries() {
+        Parcelable[] bundles = mFields.getParcelableArray(FIELD_LIST_ENTRIES);
+        if (bundles == null) {
+            return null;
+        }
+        ArrayList<ComplicationData> entries = new ArrayList<>();
+        for (Parcelable parcelable : bundles) {
+            Bundle bundle = (Bundle) parcelable;
+            bundle.setClassLoader(getClass().getClassLoader());
+            entries.add(new ComplicationData(bundle.getInt(FIELD_LIST_ENTRY_TYPE), bundle));
+        }
+        return entries;
+    }
+
     /**
      * Sets the {@link ComponentName} of the ComplicationDataSourceService that provided this
      * ComplicationData.
@@ -1243,6 +1377,35 @@
                 mFields.getBundle(FIELD_PLACEHOLDER_FIELDS));
     }
 
+    /** Returns the bytes of the proto layout. */
+    @Nullable
+    public byte[] getInteractiveLayout() {
+        return mFields.getByteArray(FIELD_PROTO_LAYOUT_INTERACTIVE);
+    }
+
+    /**
+     * Returns the list style hint.
+     *
+     * <p>Valid only if the type of this complication data is {@link #TYPE_LIST}. Otherwise returns
+     * zero.
+     */
+    public int getListStyleHint() {
+        checkFieldValidForType(FIELD_LIST_STYLE_HINT, mType);
+        return mFields.getInt(FIELD_LIST_STYLE_HINT);
+    }
+
+    /** Returns the bytes of the ambient proto layout. */
+    @Nullable
+    public byte[] getAmbientLayout() {
+        return mFields.getByteArray(FIELD_PROTO_LAYOUT_AMBIENT);
+    }
+
+    /** Returns the bytes of the proto layout resources. */
+    @Nullable
+    public byte[] getLayoutResources() {
+        return mFields.getByteArray(FIELD_PROTO_LAYOUT_RESOURCES);
+    }
+
     /**
      * Returns the start time for this complication data (i.e. the first time at which it should
      * be considered active and displayed), this may be 0. See also {@link #isActiveAt(long)}.
@@ -1655,6 +1818,19 @@
         }
 
         /**
+         * Sets the list style hint
+         *
+         * <p>Valid only if the type of this complication data is {@link #TYPE_LIST}. Otherwise
+         * returns
+         * zero.
+         */
+        @NonNull
+        public Builder setListStyleHint(int listStyleHint) {
+            putIntField(FIELD_LIST_STYLE_HINT, listStyleHint);
+            return this;
+        }
+
+        /**
          * Sets the <i>tap action</i> field. This is optional for any non-empty type.
          *
          * <p>The provided {@link PendingIntent} may be fired if the complication is tapped on. Note
@@ -1735,6 +1911,63 @@
         }
 
         /**
+         * Sets the ambient proto layout associated with this complication.
+         *
+         * <p>Returns this Builder to allow chaining.
+         */
+        @NonNull
+        public Builder setAmbientLayout(@NonNull byte[] ambientProtoLayout) {
+            putByteArrayField(FIELD_PROTO_LAYOUT_AMBIENT, ambientProtoLayout);
+            return this;
+        }
+
+        /**
+         * Sets the proto layout associated with this complication.
+         *
+         * <p>Returns this Builder to allow chaining.
+         */
+        @NonNull
+        public Builder setInteractiveLayout(@NonNull byte[] protoLayout) {
+            putByteArrayField(FIELD_PROTO_LAYOUT_INTERACTIVE, protoLayout);
+            return this;
+        }
+
+        /**
+         * Sets the proto layout resources associated with this complication.
+         *
+         * <p>Returns this Builder to allow chaining.
+         */
+        @NonNull
+        public Builder setLayoutResources(@NonNull byte[] resources) {
+            putByteArrayField(FIELD_PROTO_LAYOUT_RESOURCES, resources);
+            return this;
+        }
+
+        /**
+         * Sets the list of {@link ComplicationData} timeline entries.
+         *
+         * <p>Returns this Builder to allow chaining.
+         */
+        @NonNull
+        public Builder setListEntryCollection(
+                @Nullable Collection<ComplicationData> timelineEntries) {
+            if (timelineEntries == null) {
+                mFields.remove(FIELD_LIST_ENTRIES);
+            } else {
+                mFields.putParcelableArray(
+                        FIELD_LIST_ENTRIES,
+                        timelineEntries.stream()
+                                .map(
+                                        e -> {
+                                            e.mFields.putInt(FIELD_LIST_ENTRY_TYPE, e.mType);
+                                            return e.mFields;
+                                        })
+                                .toArray(Parcelable[]::new));
+            }
+            return this;
+        }
+
+        /**
          * Constructs and returns {@link ComplicationData} with the provided fields. All required
          * fields must be populated before this method is called.
          *
@@ -1780,6 +2013,12 @@
             mFields.putFloat(field, value);
         }
 
+        @SuppressLint("SyntheticAccessor")
+        private void putByteArrayField(@NonNull String field, @NonNull byte[] value) {
+            ComplicationData.checkFieldValidForType(field, mType);
+            mFields.putByteArray(field, value);
+        }
+
         /** Sets the field with obj or removes it if null. */
         @SuppressLint("SyntheticAccessor")
         private void putOrRemoveField(@NonNull String field, @Nullable Object obj) {
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationExperimental.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationExperimental.kt
new file mode 100644
index 0000000..81942d7
--- /dev/null
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationExperimental.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 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.
+ */
+
+package androidx.wear.watchface.complications.data
+
+@RequiresOptIn(
+    "This is an experimental API that may change or be removed without warning."
+)
+@Retention(AnnotationRetention.BINARY)
+annotation class ComplicationExperimental
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
index 5141d8b..8892b84 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
@@ -21,6 +21,10 @@
 import android.graphics.drawable.Icon
 import android.os.Build
 import androidx.annotation.RestrictTo
+import androidx.wear.tiles.proto.LayoutElementProto
+import androidx.wear.tiles.proto.ResourceProto
+import androidx.wear.tiles.LayoutElementBuilders
+import androidx.wear.tiles.ResourceBuilders
 import java.time.Instant
 
 /** The wire format for [ComplicationData]. */
@@ -1451,6 +1455,434 @@
 }
 
 /**
+ * A complication that contains a serialized protoLayout.
+ *
+ * @property contentDescription The content description field for accessibility and is used to
+ * describe what data the image represents. If the image is purely stylistic, and does not convey
+ * any information to the user, then provide an empty content description. If no content description
+ * is provided, a generic content description will be used instead.
+ */
+@ComplicationExperimental
+public class ProtoLayoutComplicationData
+internal
+/**
+ * @param ambientLayoutWireFormat The [LayoutElementBuilders.Layout] serialized into a
+ * [ByteArray] to be displayed when the device is ambient.
+ * @param interactiveLayoutWireFormat The [LayoutElementBuilders.Layout] serialized into a
+ * [ByteArray] to be displayed when the device is interactive.
+ * @param layoutResourcesWireFormat The [ResourceBuilders.Resources] serialized into a [ByteArray]
+ * for [interactiveLayoutWireFormat] and [ambientLayoutWireFormat].
+ */
+constructor(
+    private val ambientLayoutWireFormat: ByteArray,
+    private val interactiveLayoutWireFormat: ByteArray,
+    private val layoutResourcesWireFormat: ByteArray,
+    val contentDescription: ComplicationText?,
+    tapAction: PendingIntent?,
+    validTimeRange: TimeRange?,
+    cachedWireComplicationData: WireComplicationData?,
+    dataSource: ComponentName?
+) :
+    ComplicationData(
+        TYPE,
+        tapAction,
+        cachedWireComplicationData,
+        validTimeRange ?: TimeRange.ALWAYS,
+        dataSource = dataSource
+    ) {
+
+    /** The [LayoutElementBuilders.Layout] to be displayed when the device is ambient. */
+    public val ambientLayout: LayoutElementBuilders.Layout by lazy {
+        LayoutElementBuilders.Layout.fromProto(
+            LayoutElementProto.Layout.parseFrom(ambientLayoutWireFormat)
+        )
+    }
+
+    /** The [LayoutElementBuilders.Layout] to be displayed when the device is interactive. */
+    public val interactiveLayout: LayoutElementBuilders.Layout by lazy {
+        LayoutElementBuilders.Layout.fromProto(
+             LayoutElementProto.Layout.parseFrom(interactiveLayoutWireFormat)
+        )
+    }
+
+    /** The [ResourceBuilders.Resources] for [ambientLayout] and [interactiveLayout]. */
+    public val layoutResources: ResourceBuilders.Resources by lazy {
+        ResourceBuilders.Resources.fromProto(
+            ResourceProto.Resources.parseFrom(layoutResourcesWireFormat)
+        )
+    }
+
+    /**
+     * Builder for [ProtoLayoutComplicationData].
+     *
+     * You must at a minimum set the [ambientLayout], [interactiveLayout], [layoutResources] and
+     * [contentDescription] fields.
+     *
+     * @param ambientLayout The [LayoutElementBuilders.Layout] serialized into a [ByteArray] to be
+     * displayed when the device is ambient
+     * @param interactiveLayout The [LayoutElementBuilders.Layout] serialized into a [ByteArray] to
+     * be displayed when the device is interactive
+     * @param layoutResources The [ResourceBuilders.Resources] serialized into a [ByteArray] for
+     * [interactiveLayout] and [ambientLayout]
+     * @param contentDescription Localized description for use by screen readers
+     */
+    public class Builder(
+        private val ambientLayout: ByteArray,
+        private val interactiveLayout: ByteArray,
+        private val layoutResources: ByteArray,
+        private val contentDescription: ComplicationText
+    ) {
+        /**
+         * @param ambientLayout The [LayoutElementBuilders.Layout] to be displayed when the device
+         * is ambient
+         * @param interactiveLayout The [LayoutElementBuilders.Layout] to be displayed when the
+         * device is interactive
+         * @param resources The [ResourceBuilders.Resources] for [interactiveLayout] and
+         * [ambientLayout]
+         * @param contentDescription Localized description for use by screen readers
+         */
+        constructor(
+            ambientLayout: LayoutElementBuilders.Layout,
+            interactiveLayout: LayoutElementBuilders.Layout,
+            resources: ResourceBuilders.Resources,
+            contentDescription: ComplicationText
+        ) : this(
+            ambientLayout.toProto().toByteArray(),
+            interactiveLayout.toProto().toByteArray(),
+            resources.toProto().toByteArray(),
+            contentDescription
+        )
+
+        private var tapAction: PendingIntent? = null
+        private var validTimeRange: TimeRange? = null
+        private var cachedWireComplicationData: WireComplicationData? = null
+        private var dataSource: ComponentName? = null
+
+        /** Sets optional pending intent to be invoked when the complication is tapped. */
+        public fun setTapAction(tapAction: PendingIntent?): Builder = apply {
+            this.tapAction = tapAction
+        }
+
+        /** Sets optional time range during which the complication has to be shown. */
+        @Suppress("MissingGetterMatchingBuilder") // b/174052810
+        public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply {
+            this.validTimeRange = validTimeRange
+        }
+
+        /**
+         * Sets the [ComponentName] of the ComplicationDataSourceService that provided this
+         * ComplicationData, if any.
+         *
+         * Note a ComplicationDataSourceService does not need to call this because the system will
+         * set this value on its behalf.
+         */
+        public fun setDataSource(dataSource: ComponentName?): Builder = apply {
+            this.dataSource = dataSource
+        }
+
+        internal fun setCachedWireComplicationData(
+            cachedWireComplicationData: WireComplicationData?
+        ): Builder = apply { this.cachedWireComplicationData = cachedWireComplicationData }
+
+        /** Builds the [ProtoLayoutComplicationData]. */
+        public fun build(): ProtoLayoutComplicationData =
+            ProtoLayoutComplicationData(
+                ambientLayout,
+                interactiveLayout,
+                layoutResources,
+                contentDescription,
+                tapAction,
+                validTimeRange,
+                cachedWireComplicationData,
+                dataSource
+            )
+    }
+
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    override fun asWireComplicationData(): WireComplicationData {
+        cachedWireComplicationData?.let {
+            return it
+        }
+        return createWireComplicationDataBuilder()
+            .apply { fillWireComplicationDataBuilder(this) }
+            .build()
+            .also { cachedWireComplicationData = it }
+    }
+
+    override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
+        builder.setInteractiveLayout(interactiveLayoutWireFormat)
+        builder.setAmbientLayout(ambientLayoutWireFormat)
+        builder.setLayoutResources(layoutResourcesWireFormat)
+        builder.setContentDescription(
+            when (contentDescription) {
+                ComplicationText.EMPTY -> null
+                else -> contentDescription?.toWireComplicationText()
+            }
+        )
+        builder.setTapAction(tapAction)
+        setValidTimeRange(validTimeRange, builder)
+        builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as ProtoLayoutComplicationData
+
+        if (!interactiveLayoutWireFormat.contentEquals(other.interactiveLayoutWireFormat))
+            return false
+        if (!ambientLayoutWireFormat.contentEquals(other.ambientLayoutWireFormat)) return false
+        if (!layoutResourcesWireFormat.contentEquals(other.layoutResourcesWireFormat)) return false
+        if (contentDescription != other.contentDescription) return false
+        if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
+        if (tapAction != other.tapAction) return false
+        if (validTimeRange != other.validTimeRange) return false
+        if (dataSource != other.dataSource) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = interactiveLayoutWireFormat.hashCode()
+        result = 31 * result + ambientLayoutWireFormat.hashCode()
+        result = 31 * result + layoutResourcesWireFormat.hashCode()
+        result = 31 * result + (contentDescription?.hashCode() ?: 0)
+        result = 31 * result + tapActionLostDueToSerialization.hashCode()
+        result = 31 * result + (tapAction?.hashCode() ?: 0)
+        result = 31 * result + validTimeRange.hashCode()
+        result = 31 * result + dataSource.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ProtoLayoutComplicationData(protoLayoutWireFormat=$interactiveLayoutWireFormat, " +
+            "ambientProtoLayoutWireFormat=$ambientLayoutWireFormat, " +
+            "resourcesWireFormat=$layoutResourcesWireFormat, " +
+            "contentDescription=$contentDescription, " +
+            "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
+            "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource)"
+    }
+
+    /** @hide */
+    public companion object {
+        /** The [ComplicationType] corresponding to objects of this type. */
+        @JvmField
+        public val TYPE: ComplicationType = ComplicationType.PROTO_LAYOUT
+    }
+}
+
+/**
+ * A complication that's a list of other complications, typically rendered as a table. E.g. the
+ * weather forecast for the next three days could consist of three [ShortTextComplicationData]s
+ * displayed in a row of columns.
+ *
+ * @property complicationList The list of sub [ComplicationData]s to display. This has a maximum
+ * size of [ListComplicationData.MAX_ITEMS]. Note complicationList may not include a
+ * ListComplicationData.
+ * @property contentDescription The content description field for accessibility and is used to
+ * describe what data the image represents. If the image is purely stylistic, and does not convey
+ * any information to the user, then provide an empty content description. If no content description
+ * is provided, a generic content description will be used instead.
+ * @property styleHint The [StyleHint] which influences layout.
+ */
+@ComplicationExperimental
+public class ListComplicationData
+internal constructor(
+    public val complicationList: List<ComplicationData>,
+    public val contentDescription: ComplicationText?,
+    tapAction: PendingIntent?,
+    validTimeRange: TimeRange?,
+    cachedWireComplicationData: WireComplicationData?,
+    dataSource: ComponentName?,
+    public val styleHint: StyleHint
+) : ComplicationData(
+        TYPE,
+        tapAction = tapAction,
+        cachedWireComplicationData = cachedWireComplicationData,
+        validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
+        dataSource = dataSource
+    ) {
+
+    init {
+        require(complicationList.size <= MAX_ITEMS) {
+            "complicationList has a maximum of $MAX_ITEMS entries, but found " +
+                complicationList.size
+        }
+
+        for (entry in complicationList) {
+            require(entry !is ListComplicationData) {
+                "You may not include a ListComplicationData inside a ListComplicationData"
+            }
+        }
+    }
+
+    /** A hint for generating a layout for [ListComplicationData]. */
+    @ComplicationExperimental
+    enum class StyleHint(private val wireType: Int) {
+        /** Hints the list should be displayed as a single column where the entries are rows. */
+        ColumnOfRows(0),
+
+        /** Hints the list should be displayed as a single row where the entries are columns. */
+        RowOfColumns(1);
+
+        override fun toString(): String {
+            return "ListComplicationLayoutStyleHint(wireType=$wireType)"
+        }
+
+        internal companion object {
+            fun fromWireFormat(wireType: Int): StyleHint =
+                when (wireType) {
+                    ColumnOfRows.ordinal -> ColumnOfRows
+                    RowOfColumns.ordinal -> RowOfColumns
+                    else ->
+                        throw java.lang.IllegalArgumentException(
+                            "Unrecognized ListComplicationLayoutStyleHint wireType $wireType"
+                        )
+                }
+        }
+    }
+
+    /**
+     * Builder for [ListComplicationData].
+     *
+     * You must at a minimum set the [complicationList], [styleHint] and [contentDescription]
+     * fields.
+     *
+     * @param complicationList The list [ComplicationData] to be displayed, typically as a table.
+     * Note complicationList may not include a ListComplicationData.
+     * @param styleHint The [StyleHint] which influences layout.
+     * @param contentDescription Localized description for use by screen readers
+     */
+    public class Builder(
+        private val complicationList: List<ComplicationData>,
+        private val styleHint: StyleHint,
+        private val contentDescription: ComplicationText
+    ) {
+        private var tapAction: PendingIntent? = null
+        private var validTimeRange: TimeRange? = null
+        private var cachedWireComplicationData: WireComplicationData? = null
+        private var dataSource: ComponentName? = null
+
+        /** Sets optional pending intent to be invoked when the complication is tapped. */
+        @SuppressWarnings("MissingGetterMatchingBuilder") // See http://b/174052810
+        public fun setTapAction(tapAction: PendingIntent?): Builder = apply {
+            this.tapAction = tapAction
+        }
+
+        /** Sets optional time range during which the complication has to be shown. */
+        @SuppressWarnings("MissingGetterMatchingBuilder") // See http://b/174052810
+        public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply {
+            this.validTimeRange = validTimeRange
+        }
+
+        /**
+         * Sets the [ComponentName] of the ComplicationDataSourceService that provided this
+         * ComplicationData, if any.
+         *
+         * Note a ComplicationDataSourceService does not need to call this because the system will
+         * set this value on its behalf.
+         */
+        public fun setDataSource(dataSource: ComponentName?): Builder = apply {
+            this.dataSource = dataSource
+        }
+
+        internal fun setCachedWireComplicationData(
+            cachedWireComplicationData: WireComplicationData?
+        ): Builder = apply { this.cachedWireComplicationData = cachedWireComplicationData }
+
+        /** Builds the [ListComplicationData]. */
+        public fun build(): ListComplicationData =
+            ListComplicationData(
+                complicationList,
+                contentDescription,
+                tapAction,
+                validTimeRange,
+                cachedWireComplicationData,
+                dataSource,
+                styleHint
+            )
+    }
+
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    override fun asWireComplicationData(): WireComplicationData {
+        cachedWireComplicationData?.let {
+            return it
+        }
+        return createWireComplicationDataBuilder()
+            .apply { fillWireComplicationDataBuilder(this) }
+            .build()
+            .also { cachedWireComplicationData = it }
+    }
+
+    override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
+        builder.setListEntryCollection(complicationList.map {
+            it.asWireComplicationData()
+        })
+        builder.setListStyleHint(styleHint.ordinal)
+        builder.setContentDescription(
+            when (contentDescription) {
+                ComplicationText.EMPTY -> null
+                else -> contentDescription?.toWireComplicationText()
+            }
+        )
+        builder.setTapAction(tapAction)
+        setValidTimeRange(validTimeRange, builder)
+        builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
+    }
+
+    override fun hasPlaceholderFields() = false
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as ListComplicationData
+
+        if (complicationList != other.complicationList) return false
+        if (contentDescription != other.contentDescription) return false
+        if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
+        if (tapAction != other.tapAction) return false
+        if (validTimeRange != other.validTimeRange) return false
+        if (dataSource != other.dataSource) return false
+        if (styleHint != other.styleHint) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = complicationList.hashCode()
+        result = 31 * result + (contentDescription?.hashCode() ?: 0)
+        result = 31 * result + tapActionLostDueToSerialization.hashCode()
+        result = 31 * result + (tapAction?.hashCode() ?: 0)
+        result = 31 * result + validTimeRange.hashCode()
+        result = 31 * result + dataSource.hashCode()
+        result = 31 * result + styleHint.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ListComplicationData(complicationList=$complicationList, " +
+            "contentDescription=$contentDescription, " +
+            "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
+            "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " +
+            "styleHint=$styleHint)"
+    }
+
+    /** @hide */
+    public companion object {
+        /** The [ComplicationType] corresponding to objects of this type. */
+        @JvmField
+        public val TYPE: ComplicationType = ComplicationType.LIST
+
+        /** The maximum number of items in [ListComplicationData.complicationList]. */
+        public const val MAX_ITEMS = 5
+    }
+}
+
+/**
  * Type sent by the system when the watch face does not have permission to receive complication
  * data.
  *
@@ -1606,6 +2038,7 @@
     }
 }
 
+@OptIn(ComplicationExperimental::class)
 internal fun WireComplicationData.toPlaceholderComplicationData(): ComplicationData? = when (type) {
     NoDataComplicationData.TYPE.toWireComplicationType() -> null
 
@@ -1681,12 +2114,40 @@
             dataSource
         )
 
+    // TODO(b/230102159): We need to build support for placeholder ProtoLayoutComplicationData.
+    ProtoLayoutComplicationData.TYPE.toWireComplicationType() ->
+        ProtoLayoutComplicationData.Builder(
+            ambientLayout!!,
+            interactiveLayout!!,
+            layoutResources!!,
+            contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
+        )
+            .apply {
+                setTapAction(tapAction)
+                setValidTimeRange(parseTimeRange())
+                setDataSource(dataSource)
+            }
+            .build()
+
+    ListComplicationData.TYPE.toWireComplicationType() ->
+        ListComplicationData.Builder(
+            listEntries!!.map { it.toApiComplicationData() },
+            ListComplicationData.StyleHint.fromWireFormat(listStyleHint),
+            contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
+        )
+            .apply {
+                setTapAction(tapAction)
+                setValidTimeRange(parseTimeRange())
+                setDataSource(dataSource)
+            }
+            .build()
     else -> null
 }
 
 /**
  * @hide
  */
+@OptIn(ComplicationExperimental::class)
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public fun WireComplicationData.toApiComplicationData(): ComplicationData {
     val wireComplicationData = this
@@ -1778,6 +2239,35 @@
                 setDataSource(dataSource)
             }.build()
 
+        ProtoLayoutComplicationData.TYPE.toWireComplicationType() ->
+            ProtoLayoutComplicationData.Builder(
+                ambientLayout!!,
+                interactiveLayout!!,
+                layoutResources!!,
+                contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
+            )
+                .apply {
+                    setTapAction(tapAction)
+                    setValidTimeRange(parseTimeRange())
+                    setCachedWireComplicationData(wireComplicationData)
+                    setDataSource(dataSource)
+                }
+                .build()
+
+        ListComplicationData.TYPE.toWireComplicationType() ->
+            ListComplicationData.Builder(
+                listEntries!!.map { it.toApiComplicationData() },
+                ListComplicationData.StyleHint.fromWireFormat(listStyleHint),
+                contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
+            )
+                .apply {
+                    setTapAction(tapAction)
+                    setValidTimeRange(parseTimeRange())
+                    setCachedWireComplicationData(wireComplicationData)
+                    setDataSource(dataSource)
+                }
+                .build()
+
         NoPermissionComplicationData.TYPE.toWireComplicationType() ->
             NoPermissionComplicationData.Builder().apply {
                 setMonochromaticImage(parseIcon())
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
index efb2f76..76cf6e7 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
@@ -550,13 +550,21 @@
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
-        if (javaClass != other?.javaClass) return false
-
-        other as DelegatingComplicationText
-
-        if (delegate != other.delegate) return false
-
-        return true
+        when (other) {
+            is DelegatingComplicationText -> {
+                return delegate == other.delegate
+            }
+            is PlainComplicationText -> {
+                return other.toWireComplicationText() == delegate
+            }
+            is TimeDifferenceComplicationText -> {
+                return other.toWireComplicationText() == delegate
+            }
+            is TimeFormatComplicationText -> {
+                return other.toWireComplicationText() == delegate
+            }
+        }
+        return false
     }
 
     override fun hashCode(): Int {
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Type.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Type.kt
index ab7ecb0..f40a21a 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Type.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Type.kt
@@ -35,7 +35,13 @@
     MONOCHROMATIC_IMAGE(WireComplicationData.TYPE_ICON),
     SMALL_IMAGE(WireComplicationData.TYPE_SMALL_IMAGE),
     PHOTO_IMAGE(WireComplicationData.TYPE_LARGE_IMAGE),
-    NO_PERMISSION(WireComplicationData.TYPE_NO_PERMISSION);
+    NO_PERMISSION(WireComplicationData.TYPE_NO_PERMISSION),
+
+    @ComplicationExperimental
+    PROTO_LAYOUT(WireComplicationData.TYPE_PROTO_LAYOUT),
+
+    @ComplicationExperimental
+    LIST(WireComplicationData.TYPE_LIST);
 
     /**
      * Converts this value to the integer value used for serialization.
@@ -57,6 +63,7 @@
          *
          * @hide
          */
+        @OptIn(ComplicationExperimental::class)
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @JvmStatic
         public fun fromWireType(wireType: Int): ComplicationType =
@@ -71,6 +78,8 @@
                 SMALL_IMAGE.wireType -> SMALL_IMAGE
                 PHOTO_IMAGE.wireType -> PHOTO_IMAGE
                 NO_PERMISSION.wireType -> NO_PERMISSION
+                PROTO_LAYOUT.wireType -> PROTO_LAYOUT
+                LIST.wireType -> LIST
                 else -> EMPTY
             }
 
diff --git a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
index f1945a4..573eda8 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
@@ -456,6 +456,113 @@
     }
 
     @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun listComplicationData() {
+        val data = ListComplicationData.Builder(
+            listOf(
+                ShortTextComplicationData.Builder("text".complicationText, ComplicationText.EMPTY)
+                    .build(),
+                RangedValueComplicationData.Builder(
+                    value = 95f,
+                    min = 0f,
+                    max = 100f,
+                    ComplicationText.EMPTY
+                ).build()
+            ),
+            ListComplicationData.StyleHint.RowOfColumns,
+            ComplicationText.EMPTY
+        ).setDataSource(dataSourceA).build()
+
+        ParcelableSubject.assertThat(data.asWireComplicationData())
+            .hasSameSerializationAs(
+                WireComplicationDataBuilder(WireComplicationData.TYPE_LIST)
+                    .setListEntryCollection(
+                        listOf(
+                            WireComplicationDataBuilder(WireComplicationData.TYPE_SHORT_TEXT)
+                                .setShortText(WireComplicationText.plainText("text"))
+                                .build(),
+                            WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+                                .setRangedValue(95f)
+                                .setRangedMinValue(0f)
+                                .setRangedMaxValue(100f)
+                                .build()
+                        )
+                    )
+                    .setListStyleHint(ListComplicationData.StyleHint.RowOfColumns.ordinal)
+                    .setDataSource(dataSourceA)
+                    .build()
+            )
+        testRoundTripConversions(data)
+        val deserialized = serializeAndDeserialize(data) as ListComplicationData
+        assertThat(deserialized.complicationList).containsExactly(
+            ShortTextComplicationData.Builder("text".complicationText, ComplicationText.EMPTY)
+                .build(),
+            RangedValueComplicationData.Builder(
+                value = 95f,
+                min = 0f,
+                max = 100f,
+                ComplicationText.EMPTY
+            ).build()
+        )
+        assertThat(deserialized.styleHint).isEqualTo(ListComplicationData.StyleHint.RowOfColumns)
+
+        val data2 = ListComplicationData.Builder(
+            listOf(
+                ShortTextComplicationData.Builder("text".complicationText, ComplicationText.EMPTY)
+                    .build(),
+                RangedValueComplicationData.Builder(
+                    value = 95f,
+                    min = 0f,
+                    max = 100f,
+                    ComplicationText.EMPTY
+                ).build()
+            ),
+            ListComplicationData.StyleHint.RowOfColumns,
+            ComplicationText.EMPTY
+        ).setDataSource(dataSourceA).build()
+
+        val data3 = ListComplicationData.Builder(
+            listOf(
+                ShortTextComplicationData.Builder("text2".complicationText, ComplicationText.EMPTY)
+                    .build(),
+                RangedValueComplicationData.Builder(
+                    value = 95f,
+                    min = 0f,
+                    max = 100f,
+                    ComplicationText.EMPTY
+                ).build()
+            ),
+            ListComplicationData.StyleHint.RowOfColumns,
+            ComplicationText.EMPTY
+        ).setDataSource(dataSourceB).build()
+
+        assertThat(data).isEqualTo(data2)
+        assertThat(data).isNotEqualTo(data3)
+        assertThat(data.hashCode()).isEqualTo(data2.hashCode())
+        assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
+        assertThat(data.toString()).isEqualTo(
+            "ListComplicationData(complicationList=[ShortTextComplicationData(text=" +
+                "ComplicationText{mSurroundingText=text, mTimeDependentText=null}, title=null, " +
+                "monochromaticImage=null, contentDescription=ComplicationText{mSurroundingText=, " +
+                "mTimeDependentText=null}, tapActionLostDueToSerialization=false, tapAction=null," +
+                " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
+                "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), dataSource=null), " +
+                "RangedValueComplicationData(value=95.0, min=0.0, max=100.0, " +
+                "monochromaticImage=null, title=null, text=null, contentDescription=" +
+                "ComplicationText{mSurroundingText=, mTimeDependentText=null}), " +
+                "tapActionLostDueToSerialization=false, tapAction=null, validTimeRange=" +
+                "TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
+                "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
+                "dataSource=null)], contentDescription=ComplicationText{mSurroundingText=, " +
+                "mTimeDependentText=null}, tapActionLostDueToSerialization=false, " +
+                "tapAction=null, validTimeRange=TimeRange(startDateTimeMillis=" +
+                "-1000000000-01-01T00:00:00Z, endDateTimeMillis=+1000000000-12-31T23:59:" +
+                "59.999999999Z), dataSource=ComponentInfo{com.pkg_a/com.a}, styleHint=" +
+                "ListComplicationLayoutStyleHint(wireType=1))"
+        )
+    }
+
+    @Test
     public fun noDataComplicationData_shortText() {
         val data = NoDataComplicationData(
             ShortTextComplicationData.Builder(
@@ -979,6 +1086,47 @@
     }
 
     @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun protoLayoutComplicationData() {
+        val ambientLayout = ByteArray(1)
+        val interactiveLayout = ByteArray(2)
+        val resources = ByteArray(3)
+
+        assertRoundtrip(
+            WireComplicationDataBuilder(WireComplicationData.TYPE_PROTO_LAYOUT)
+                .setAmbientLayout(ambientLayout)
+                .setInteractiveLayout(interactiveLayout)
+                .setLayoutResources(resources)
+                .build(),
+            ComplicationType.PROTO_LAYOUT
+        )
+    }
+
+    @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun listComplicationData() {
+        val wireShortText = WireComplicationDataBuilder(WireComplicationData.TYPE_SHORT_TEXT)
+            .setShortText(WireComplicationText.plainText("text"))
+            .build()
+
+        val wireRangedValue = WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+            .setRangedValue(95f)
+            .setRangedMinValue(0f)
+            .setRangedMaxValue(100f)
+            .build()
+
+        assertRoundtrip(
+            WireComplicationDataBuilder(WireComplicationData.TYPE_LIST)
+                .setListEntryCollection(listOf(wireShortText, wireRangedValue))
+                .setListStyleHint(
+                    ListComplicationData.StyleHint.RowOfColumns.ordinal
+                )
+                .build(),
+            ComplicationType.LIST
+        )
+    }
+
+    @Test
     public fun noDataComplicationData_shortText() {
         val icon = Icon.createWithContentUri("someuri")
         assertRoundtrip(
@@ -1077,6 +1225,53 @@
         )
     }
 
+    @Test
+    public fun noDataComplicationData_protoLayout() {
+        val ambientLayout = ByteArray(1)
+        val interactiveLayout = ByteArray(2)
+        val resources = ByteArray(3)
+        assertRoundtrip(
+            WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
+                .setPlaceholder(
+                    WireComplicationDataBuilder(WireComplicationData.TYPE_PROTO_LAYOUT)
+                        .setAmbientLayout(ambientLayout)
+                        .setInteractiveLayout(interactiveLayout)
+                        .setLayoutResources(resources)
+                        .build()
+                )
+                .build(),
+            ComplicationType.NO_DATA
+        )
+    }
+
+    @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun noDataComplicationData_list() {
+        val wireShortText = WireComplicationDataBuilder(WireComplicationData.TYPE_SHORT_TEXT)
+        .setShortText(WireComplicationText.plainText("text"))
+        .build()
+
+        val wireRangedValue = WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+            .setRangedValue(95f)
+            .setRangedMinValue(0f)
+            .setRangedMaxValue(100f)
+            .build()
+
+        assertRoundtrip(
+            WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
+                .setPlaceholder(
+                    WireComplicationDataBuilder(WireComplicationData.TYPE_LIST)
+                        .setListEntryCollection(listOf(wireShortText, wireRangedValue))
+                        .setListStyleHint(
+                            ListComplicationData.StyleHint.RowOfColumns.ordinal
+                        )
+                        .build(),
+                )
+                .build(),
+            ComplicationType.NO_DATA
+        )
+    }
+
     private fun assertRoundtrip(wireData: WireComplicationData, type: ComplicationType) {
         val data = wireData.toApiComplicationData()
         assertThat(data.type).isEqualTo(type)
@@ -1170,6 +1365,44 @@
             ).asWireComplicationData().placeholder?.tapAction
         ).isEqualTo(mPendingIntent)
     }
+
+    @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun protoLayoutComplicationData() {
+        val ambientLayout = ByteArray(1)
+        val interactiveLayout = ByteArray(2)
+        val resources = ByteArray(3)
+
+        assertThat(
+            ProtoLayoutComplicationData.Builder(
+                ambientLayout,
+                interactiveLayout,
+                resources,
+                ComplicationText.EMPTY
+            )
+                .setTapAction(mPendingIntent)
+                .build()
+                .tapAction
+        ).isEqualTo(mPendingIntent)
+    }
+
+    @OptIn(ComplicationExperimental::class)
+    @Test
+    public fun listComplicationData() {
+        val icon = Icon.createWithContentUri("someuri")
+        val image = SmallImage.Builder(icon, SmallImageType.PHOTO).build()
+        assertThat(
+            ListComplicationData.Builder(
+                listOf(
+                    SmallImageComplicationData.Builder(image, ComplicationText.EMPTY).build()
+                ),
+                ListComplicationData.StyleHint.RowOfColumns,
+                ComplicationText.EMPTY
+            )
+                .setTapAction(mPendingIntent).build()
+                .tapAction
+        ).isEqualTo(mPendingIntent)
+    }
 }
 
 @RunWith(SharedRobolectricTestRunner::class)
@@ -1251,7 +1484,7 @@
     }
 
     @Test
-    public fun NoDataComplicationData() {
+    public fun noDataComplicationData() {
         assertThat(
             NoDataComplicationData(
                 MonochromaticImageComplicationData.Builder(
@@ -1261,6 +1494,46 @@
             ).asWireComplicationData().toApiComplicationData().tapAction
         ).isEqualTo(mPendingIntent)
     }
+
+    @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun protoLayoutComplicationData() {
+        val ambientLayout = ByteArray(1)
+        val interactiveLayout = ByteArray(2)
+        val resources = ByteArray(3)
+
+        assertThat(
+            ProtoLayoutComplicationData.Builder(
+                ambientLayout,
+                interactiveLayout,
+                resources,
+                ComplicationText.EMPTY
+            )
+                .setTapAction(mPendingIntent)
+                .build()
+                .asWireComplicationData().toApiComplicationData().tapAction
+        ).isEqualTo(mPendingIntent)
+    }
+
+    @OptIn(ComplicationExperimental::class)
+    @Test
+    public fun listComplicationData() {
+        val icon = Icon.createWithContentUri("someuri")
+        val image = SmallImage.Builder(icon, SmallImageType.PHOTO).build()
+        assertThat(
+            ListComplicationData.Builder(
+                listOf(
+                    SmallImageComplicationData.Builder(image, ComplicationText.EMPTY).build()
+                ),
+                ListComplicationData.StyleHint.RowOfColumns,
+                ComplicationText.EMPTY
+            )
+                .setTapAction(mPendingIntent).build()
+                .asWireComplicationData()
+                .toApiComplicationData()
+                .tapAction
+        ).isEqualTo(mPendingIntent)
+    }
 }
 
 @RunWith(SharedRobolectricTestRunner::class)
@@ -1372,6 +1645,80 @@
     }
 
     @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun protoLayout() {
+        val ambientLayout = ByteArray(1)
+        val interactiveLayout = ByteArray(2)
+        val resources = ByteArray(3)
+
+        val data = ProtoLayoutComplicationData.Builder(
+            ambientLayout,
+            interactiveLayout,
+            resources,
+            ComplicationText.EMPTY
+        )
+            .setValidTimeRange(TimeRange.between(testStartInstant, testEndDateInstant))
+            .build()
+
+        ParcelableSubject.assertThat(data.asWireComplicationData())
+            .hasSameSerializationAs(
+                WireComplicationDataBuilder(WireComplicationData.TYPE_PROTO_LAYOUT)
+                    .setAmbientLayout(ambientLayout)
+                    .setInteractiveLayout(interactiveLayout)
+                    .setLayoutResources(resources)
+                    .setStartDateTimeMillis(testStartInstant.toEpochMilli())
+                    .setEndDateTimeMillis(testEndDateInstant.toEpochMilli())
+                    .build()
+            )
+    }
+
+    @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun list() {
+        val data = ListComplicationData.Builder(
+            listOf(
+                ShortTextComplicationData.Builder(
+                    "text".complicationText,
+                    ComplicationText.EMPTY
+                )
+                    .build(),
+                RangedValueComplicationData.Builder(
+                    value = 95f,
+                    min = 0f,
+                    max = 100f,
+                    ComplicationText.EMPTY
+                ).build()
+            ),
+            ListComplicationData.StyleHint.RowOfColumns,
+            ComplicationText.EMPTY
+        )
+            .setValidTimeRange(TimeRange.between(testStartInstant, testEndDateInstant))
+            .build()
+
+        val wireShortText = WireComplicationDataBuilder(WireComplicationData.TYPE_SHORT_TEXT)
+            .setShortText(WireComplicationText.plainText("text"))
+            .build()
+
+        val wireRangedValue = WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+            .setRangedValue(95f)
+            .setRangedMinValue(0f)
+            .setRangedMaxValue(100f)
+            .build()
+
+        ParcelableSubject.assertThat(data.asWireComplicationData())
+            .hasSameSerializationAs(
+                WireComplicationDataBuilder(WireComplicationData.TYPE_LIST)
+                    .setListEntryCollection(listOf(wireShortText, wireRangedValue))
+                    .setListStyleHint(
+                        ListComplicationData.StyleHint.RowOfColumns.ordinal
+                    )
+                    .setStartDateTimeMillis(testStartInstant.toEpochMilli())
+                    .setEndDateTimeMillis(testEndDateInstant.toEpochMilli())
+                    .build()
+            )
+    }
+
+    @Test
     public fun noDataComplicationData_shortText() {
         val data = NoDataComplicationData(
             ShortTextComplicationData.Builder("text".complicationText, ComplicationText.EMPTY)
@@ -1487,6 +1834,83 @@
                     .build()
             )
     }
+
+    @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun noDataComplicationData_protoLayout() {
+        val ambientLayout = ByteArray(1)
+        val interactiveLayout = ByteArray(2)
+        val resources = ByteArray(3)
+
+        val data = NoDataComplicationData(
+            ProtoLayoutComplicationData.Builder(
+                ambientLayout,
+                interactiveLayout,
+                resources,
+                ComplicationText.EMPTY
+            ).build()
+        )
+        ParcelableSubject.assertThat(data.asWireComplicationData())
+            .hasSameSerializationAs(
+                WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_PROTO_LAYOUT)
+                            .setAmbientLayout(ambientLayout)
+                            .setInteractiveLayout(interactiveLayout)
+                            .setLayoutResources(resources)
+                            .build()
+                    )
+                    .build()
+            )
+    }
+
+    @Test
+    @OptIn(ComplicationExperimental::class)
+    public fun noDataComplicationData_list() {
+        val data = NoDataComplicationData(
+            ListComplicationData.Builder(
+                listOf(
+                    ShortTextComplicationData.Builder(
+                        "text".complicationText,
+                        ComplicationText.EMPTY
+                    )
+                        .build(),
+                    RangedValueComplicationData.Builder(
+                        value = 95f,
+                        min = 0f,
+                        max = 100f,
+                        ComplicationText.EMPTY
+                    ).build()
+                ),
+                ListComplicationData.StyleHint.RowOfColumns,
+                ComplicationText.EMPTY
+            ).build()
+        )
+
+        val wireShortText = WireComplicationDataBuilder(WireComplicationData.TYPE_SHORT_TEXT)
+            .setShortText(WireComplicationText.plainText("text"))
+            .build()
+
+        val wireRangedValue = WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+            .setRangedValue(95f)
+            .setRangedMinValue(0f)
+            .setRangedMaxValue(100f)
+            .build()
+
+        ParcelableSubject.assertThat(data.asWireComplicationData())
+            .hasSameSerializationAs(
+                WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_LIST)
+                            .setListEntryCollection(listOf(wireShortText, wireRangedValue))
+                            .setListStyleHint(
+                                ListComplicationData.StyleHint.RowOfColumns.ordinal
+                            )
+                            .build()
+                    )
+                    .build()
+            )
+    }
 }
 
 val String.complicationText get() = PlainComplicationText.Builder(this).build()
diff --git a/wear/watchface/watchface-samples-minimal-style/src/main/java/androidx/wear/watchface/samples/minimal/style/ConfigActivity.java b/wear/watchface/watchface-samples-minimal-style/src/main/java/androidx/wear/watchface/samples/minimal/style/ConfigActivity.java
index 288ba72..efa7de5 100644
--- a/wear/watchface/watchface-samples-minimal-style/src/main/java/androidx/wear/watchface/samples/minimal/style/ConfigActivity.java
+++ b/wear/watchface/watchface-samples-minimal-style/src/main/java/androidx/wear/watchface/samples/minimal/style/ConfigActivity.java
@@ -35,6 +35,8 @@
 
 import java.util.concurrent.Executor;
 
+import kotlin.OptIn;
+
 /** Configuration activity for the watch face. */
 public class ConfigActivity extends ComponentActivity {
 
@@ -99,6 +101,7 @@
 
         MutableUserStyle userStyle = mEditorSession.getUserStyle().getValue().toMutableUserStyle();
         ListOption currentOption = (ListOption) userStyle.get(mTimeStyleId);
+        @OptIn(markerClass = androidx.wear.watchface.style.ExperimentalHierarchicalStyle.class)
         ListUserStyleSetting listUserStyleSetting =
                 (ListUserStyleSetting) mEditorSession.getUserStyleSchema()
                         .getRootUserStyleSettings()