Merge "Adding support for nested map return types in DAO functions of Room." into androidx-main
diff --git a/activity/activity-ktx/OWNERS b/activity/activity-ktx/OWNERS
index 6fd8227..40fcc5c 100644
--- a/activity/activity-ktx/OWNERS
+++ b/activity/activity-ktx/OWNERS
@@ -1 +1,2 @@
+# Bug component: 530242
 yboyar@google.com
diff --git a/activity/integration-tests/baselineprofile/build.gradle b/activity/integration-tests/baselineprofile/build.gradle
index 33b81d3..51e930b 100644
--- a/activity/integration-tests/baselineprofile/build.gradle
+++ b/activity/integration-tests/baselineprofile/build.gradle
@@ -40,8 +40,8 @@
 }
 
 dependencies {
-    implementation(project(":benchmark:benchmark-junit4"))
-    implementation(project(":benchmark:benchmark-macro-junit4"))
+    implementation(projectOrArtifact(":benchmark:benchmark-junit4"))
+    implementation(projectOrArtifact(":benchmark:benchmark-macro-junit4"))
     implementation(libs.testRules)
     implementation(libs.testExtJunit)
     implementation(libs.testCore)
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.kt
index bc37342..fed365e 100644
--- a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.kt
+++ b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.kt
@@ -25,7 +25,7 @@
     AnnotationTarget.PROPERTY_SETTER
 )
 @Retention(AnnotationRetention.SOURCE)
-@Deprecated("Replaced by the {@code androidx.resourceinpsection} package.")
+@Deprecated("Replaced by the androidx.resourceinpsection package.")
 public annotation class InspectableProperty(
     /**
      * The name of the property.
diff --git a/appactions/builtintypes/OWNERS b/appactions/builtintypes/OWNERS
index a07954d..671da19 100644
--- a/appactions/builtintypes/OWNERS
+++ b/appactions/builtintypes/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 1292315
 raghavmehta@google.com
 ddong@google.com
diff --git a/appactions/builtintypes/builtintypes/api/current.txt b/appactions/builtintypes/builtintypes/api/current.txt
index be0a503..fe310a0 100644
--- a/appactions/builtintypes/builtintypes/api/current.txt
+++ b/appactions/builtintypes/builtintypes/api/current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.appactions.builtintypes.properties {
 
-  public final class ByDay {
+  @androidx.appsearch.annotation.Document(name="bitprop:ByDay") public final class ByDay {
     ctor public ByDay(androidx.appactions.builtintypes.types.DayOfWeek dayOfWeek);
     ctor public ByDay(String text);
     method public androidx.appactions.builtintypes.types.DayOfWeek? getAsDayOfWeek();
@@ -17,7 +17,7 @@
     method public default R text(String instance);
   }
 
-  public final class DisambiguatingDescription {
+  @androidx.appsearch.annotation.Document(name="bitprop:DisambiguatingDescription") public final class DisambiguatingDescription {
     ctor public DisambiguatingDescription(androidx.appactions.builtintypes.properties.DisambiguatingDescription.CanonicalValue canonicalValue);
     ctor public DisambiguatingDescription(String text);
     method public androidx.appactions.builtintypes.properties.DisambiguatingDescription.CanonicalValue? getAsCanonicalValue();
@@ -38,7 +38,7 @@
     method public default R text(String instance);
   }
 
-  public final class EndDate {
+  @androidx.appsearch.annotation.Document(name="bitprop:EndDate") public final class EndDate {
     ctor public EndDate(java.time.Instant instant);
     ctor public EndDate(java.time.LocalDate date);
     ctor public EndDate(java.time.LocalDateTime localDateTime);
@@ -58,7 +58,7 @@
     method public R orElse();
   }
 
-  public final class EndTime {
+  @androidx.appsearch.annotation.Document(name="bitprop:EndTime") public final class EndTime {
     ctor public EndTime(java.time.Instant instant);
     ctor public EndTime(java.time.LocalDateTime localDateTime);
     ctor public EndTime(java.time.LocalTime time);
@@ -78,7 +78,7 @@
     method public default R time(java.time.LocalTime instance);
   }
 
-  public final class ExceptDate {
+  @androidx.appsearch.annotation.Document(name="bitprop:ExceptDate") public final class ExceptDate {
     ctor public ExceptDate(java.time.Instant instant);
     ctor public ExceptDate(java.time.LocalDate date);
     ctor public ExceptDate(java.time.LocalDateTime localDateTime);
@@ -98,13 +98,13 @@
     method public R orElse();
   }
 
-  public final class Name {
+  @androidx.appsearch.annotation.Document(name="bitprop:Name") public final class Name {
     ctor public Name(String text);
     method public String? getAsText();
     property public final String? asText;
   }
 
-  public final class RepeatFrequency {
+  @androidx.appsearch.annotation.Document(name="bitprop:RepeatFrequency") public final class RepeatFrequency {
     ctor public RepeatFrequency(String text);
     ctor public RepeatFrequency(java.time.Duration duration);
     method public java.time.Duration? getAsDuration();
@@ -120,7 +120,7 @@
     method public default R text(String instance);
   }
 
-  public final class StartDate {
+  @androidx.appsearch.annotation.Document(name="bitprop:StartDate") public final class StartDate {
     ctor public StartDate(java.time.Instant instant);
     ctor public StartDate(java.time.LocalDate date);
     ctor public StartDate(java.time.LocalDateTime localDateTime);
@@ -140,7 +140,7 @@
     method public R orElse();
   }
 
-  public final class StartTime {
+  @androidx.appsearch.annotation.Document(name="bitprop:StartTime") public final class StartTime {
     ctor public StartTime(java.time.Instant instant);
     ctor public StartTime(java.time.LocalDateTime localDateTime);
     ctor public StartTime(java.time.LocalTime time);
@@ -175,6 +175,7 @@
     method public final String? getNamespace();
     method protected abstract String getSelfTypeName();
     method public final int hashCode();
+    method public final Boolean? isAlarmEnabled();
     method public final Builder toBuilder();
     method protected abstract Builder toBuilderWithAdditionalPropertiesOnly();
     method public final String toString();
@@ -182,6 +183,7 @@
     property public final androidx.appactions.builtintypes.types.Schedule? alarmSchedule;
     property public final androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription;
     property public final String? identifier;
+    property public final Boolean? isAlarmEnabled;
     property public final androidx.appactions.builtintypes.properties.Name? name;
     property public final String? namespace;
     property protected abstract String selfTypeName;
@@ -195,6 +197,7 @@
     method protected abstract java.util.Map<java.lang.String,java.lang.Object> getAdditionalProperties();
     method protected abstract String getSelfTypeName();
     method public final int hashCode();
+    method public final Self setAlarmEnabled(Boolean? boolean);
     method public final Self setAlarmSchedule(androidx.appactions.builtintypes.types.Schedule? schedule);
     method public final Self setDisambiguatingDescription(androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription);
     method public final Self setIdentifier(String? text);
@@ -205,6 +208,44 @@
     property protected abstract String selfTypeName;
   }
 
+  public abstract class AbstractCommonExecutionStatus<Self extends androidx.appactions.builtintypes.types.AbstractCommonExecutionStatus<Self, Builder>, Builder extends androidx.appactions.builtintypes.types.AbstractCommonExecutionStatus.Builder<Builder, Self>> implements androidx.appactions.builtintypes.types.CommonExecutionStatus {
+    ctor public AbstractCommonExecutionStatus(androidx.appactions.builtintypes.types.CommonExecutionStatus commonExecutionStatus);
+    method public final boolean equals(Object? other);
+    method protected abstract java.util.Map<java.lang.String,java.lang.Object> getAdditionalProperties();
+    method public final androidx.appactions.builtintypes.properties.DisambiguatingDescription? getDisambiguatingDescription();
+    method public final String? getIdentifier();
+    method public final androidx.appactions.builtintypes.properties.Name? getName();
+    method public final String? getNamespace();
+    method protected abstract String getSelfTypeName();
+    method public final int hashCode();
+    method public final Builder toBuilder();
+    method protected abstract Builder toBuilderWithAdditionalPropertiesOnly();
+    method public final String toString();
+    property protected abstract java.util.Map<java.lang.String,java.lang.Object> additionalProperties;
+    property public final androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription;
+    property public final String? identifier;
+    property public final androidx.appactions.builtintypes.properties.Name? name;
+    property public final String? namespace;
+    property protected abstract String selfTypeName;
+  }
+
+  public abstract static class AbstractCommonExecutionStatus.Builder<Self extends androidx.appactions.builtintypes.types.AbstractCommonExecutionStatus.Builder<Self, Built>, Built extends androidx.appactions.builtintypes.types.AbstractCommonExecutionStatus<Built, Self>> implements androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<Self> {
+    ctor public AbstractCommonExecutionStatus.Builder();
+    method public final Built build();
+    method protected abstract Built buildFromCommonExecutionStatus(androidx.appactions.builtintypes.types.CommonExecutionStatus commonExecutionStatus);
+    method public final boolean equals(Object? other);
+    method protected abstract java.util.Map<java.lang.String,java.lang.Object> getAdditionalProperties();
+    method protected abstract String getSelfTypeName();
+    method public final int hashCode();
+    method public final Self setDisambiguatingDescription(androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription);
+    method public final Self setIdentifier(String? text);
+    method public final Self setName(androidx.appactions.builtintypes.properties.Name? name);
+    method public final Self setNamespace(String? namespace);
+    method public final String toString();
+    property protected abstract java.util.Map<java.lang.String,java.lang.Object> additionalProperties;
+    property protected abstract String selfTypeName;
+  }
+
   public abstract class AbstractExecutionStatus<Self extends androidx.appactions.builtintypes.types.AbstractExecutionStatus<Self, Builder>, Builder extends androidx.appactions.builtintypes.types.AbstractExecutionStatus.Builder<Builder, Self>> implements androidx.appactions.builtintypes.types.ExecutionStatus {
     ctor public AbstractExecutionStatus(androidx.appactions.builtintypes.types.ExecutionStatus executionStatus);
     method public final boolean equals(Object? other);
@@ -367,6 +408,7 @@
     method public final androidx.appactions.builtintypes.properties.Name? getName();
     method public final String? getNamespace();
     method protected abstract String getSelfTypeName();
+    method public final String? getTelephone();
     method public final int hashCode();
     method public final Builder toBuilder();
     method protected abstract Builder toBuilderWithAdditionalPropertiesOnly();
@@ -378,6 +420,7 @@
     property public final androidx.appactions.builtintypes.properties.Name? name;
     property public final String? namespace;
     property protected abstract String selfTypeName;
+    property public final String? telephone;
   }
 
   public abstract static class AbstractPerson.Builder<Self extends androidx.appactions.builtintypes.types.AbstractPerson.Builder<Self, Built>, Built extends androidx.appactions.builtintypes.types.AbstractPerson<Built, Self>> implements androidx.appactions.builtintypes.types.Person.Builder<Self> {
@@ -393,6 +436,7 @@
     method public final Self setIdentifier(String? text);
     method public final Self setName(androidx.appactions.builtintypes.properties.Name? name);
     method public final Self setNamespace(String? namespace);
+    method public final Self setTelephone(String? text);
     method public final String toString();
     property protected abstract java.util.Map<java.lang.String,java.lang.Object> additionalProperties;
     property protected abstract String selfTypeName;
@@ -635,16 +679,19 @@
     property protected abstract String selfTypeName;
   }
 
-  public interface Alarm extends androidx.appactions.builtintypes.types.Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Alarm") public interface Alarm extends androidx.appactions.builtintypes.types.Thing {
     method public static androidx.appactions.builtintypes.types.Alarm.Builder<?> Builder();
-    method public androidx.appactions.builtintypes.types.Schedule? getAlarmSchedule();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.types.Schedule? getAlarmSchedule();
+    method @androidx.appsearch.annotation.Document.BooleanProperty public Boolean? isAlarmEnabled();
     method public androidx.appactions.builtintypes.types.Alarm.Builder<?> toBuilder();
-    property public abstract androidx.appactions.builtintypes.types.Schedule? alarmSchedule;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.types.Schedule? alarmSchedule;
+    property @androidx.appsearch.annotation.Document.BooleanProperty public abstract Boolean? isAlarmEnabled;
     field public static final androidx.appactions.builtintypes.types.Alarm.Companion Companion;
   }
 
   public static interface Alarm.Builder<Self extends androidx.appactions.builtintypes.types.Alarm.Builder<Self>> extends androidx.appactions.builtintypes.types.Thing.Builder<Self> {
     method public androidx.appactions.builtintypes.types.Alarm build();
+    method public Self setAlarmEnabled(Boolean? boolean);
     method public Self setAlarmSchedule(androidx.appactions.builtintypes.types.Schedule? schedule);
     method public default Self setDisambiguatingDescription(androidx.appactions.builtintypes.types.Alarm.DisambiguatingDescriptionValue canonicalValue);
   }
@@ -663,6 +710,20 @@
   public static final class Alarm.DisambiguatingDescriptionValue.Companion {
   }
 
+  @androidx.appsearch.annotation.Document(name="bit:CommonExecutionStatus") public interface CommonExecutionStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+    method public static androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<?> Builder();
+    method public androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<?> toBuilder();
+    field public static final androidx.appactions.builtintypes.types.CommonExecutionStatus.Companion Companion;
+  }
+
+  public static interface CommonExecutionStatus.Builder<Self extends androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.ExecutionStatus.Builder<Self> {
+    method public androidx.appactions.builtintypes.types.CommonExecutionStatus build();
+  }
+
+  public static final class CommonExecutionStatus.Companion {
+    method public androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<?> Builder();
+  }
+
   public final class DayOfWeek {
     method public String getCanonicalUrl();
     method public <R> R mapWhen(androidx.appactions.builtintypes.types.DayOfWeek.Mapper<R> mapper);
@@ -693,7 +754,7 @@
     method public default R wednesday();
   }
 
-  public interface ExecutionStatus extends androidx.appactions.builtintypes.types.Intangible {
+  @androidx.appsearch.annotation.Document(name="bit:ExecutionStatus") public interface ExecutionStatus extends androidx.appactions.builtintypes.types.Intangible {
     method public static androidx.appactions.builtintypes.types.ExecutionStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.ExecutionStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.ExecutionStatus.Companion Companion;
@@ -707,13 +768,13 @@
     method public androidx.appactions.builtintypes.types.ExecutionStatus.Builder<?> Builder();
   }
 
-  public interface GenericErrorStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+  @androidx.appsearch.annotation.Document(name="bit:GenericErrorStatus") public interface GenericErrorStatus extends androidx.appactions.builtintypes.types.CommonExecutionStatus {
     method public static androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.GenericErrorStatus.Companion Companion;
   }
 
-  public static interface GenericErrorStatus.Builder<Self extends androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.ExecutionStatus.Builder<Self> {
+  public static interface GenericErrorStatus.Builder<Self extends androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<Self> {
     method public androidx.appactions.builtintypes.types.GenericErrorStatus build();
   }
 
@@ -721,7 +782,7 @@
     method public androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<?> Builder();
   }
 
-  public interface Intangible extends androidx.appactions.builtintypes.types.Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Intangible") public interface Intangible extends androidx.appactions.builtintypes.types.Thing {
     method public static androidx.appactions.builtintypes.types.Intangible.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.Intangible.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.Intangible.Companion Companion;
@@ -735,7 +796,7 @@
     method public androidx.appactions.builtintypes.types.Intangible.Builder<?> Builder();
   }
 
-  public interface ObjectCreationLimitReachedStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+  @androidx.appsearch.annotation.Document(name="bit:ObjectCreationLimitReachedStatus") public interface ObjectCreationLimitReachedStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
     method public static androidx.appactions.builtintypes.types.ObjectCreationLimitReachedStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.ObjectCreationLimitReachedStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.ObjectCreationLimitReachedStatus.Companion Companion;
@@ -749,50 +810,53 @@
     method public androidx.appactions.builtintypes.types.ObjectCreationLimitReachedStatus.Builder<?> Builder();
   }
 
-  public interface Person extends androidx.appactions.builtintypes.types.Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Person") public interface Person extends androidx.appactions.builtintypes.types.Thing {
     method public static androidx.appactions.builtintypes.types.Person.Builder<?> Builder();
-    method public String? getEmail();
+    method @androidx.appsearch.annotation.Document.StringProperty public String? getEmail();
+    method @androidx.appsearch.annotation.Document.StringProperty public String? getTelephone();
     method public androidx.appactions.builtintypes.types.Person.Builder<?> toBuilder();
-    property public abstract String? email;
+    property @androidx.appsearch.annotation.Document.StringProperty public abstract String? email;
+    property @androidx.appsearch.annotation.Document.StringProperty public abstract String? telephone;
     field public static final androidx.appactions.builtintypes.types.Person.Companion Companion;
   }
 
   public static interface Person.Builder<Self extends androidx.appactions.builtintypes.types.Person.Builder<Self>> extends androidx.appactions.builtintypes.types.Thing.Builder<Self> {
     method public androidx.appactions.builtintypes.types.Person build();
     method public Self setEmail(String? text);
+    method public Self setTelephone(String? text);
   }
 
   public static final class Person.Companion {
     method public androidx.appactions.builtintypes.types.Person.Builder<?> Builder();
   }
 
-  public interface Schedule extends androidx.appactions.builtintypes.types.Intangible {
+  @androidx.appsearch.annotation.Document(name="bit:Schedule") public interface Schedule extends androidx.appactions.builtintypes.types.Intangible {
     method public static androidx.appactions.builtintypes.types.Schedule.Builder<?> Builder();
-    method public java.util.List<androidx.appactions.builtintypes.properties.ByDay> getByDays();
-    method public java.util.List<java.lang.Long> getByMonthDays();
-    method public java.util.List<java.lang.Long> getByMonthWeeks();
-    method public java.util.List<java.lang.Long> getByMonths();
-    method public androidx.appactions.builtintypes.properties.EndDate? getEndDate();
-    method public androidx.appactions.builtintypes.properties.EndTime? getEndTime();
-    method public androidx.appactions.builtintypes.properties.ExceptDate? getExceptDate();
-    method public Long? getRepeatCount();
-    method public androidx.appactions.builtintypes.properties.RepeatFrequency? getRepeatFrequency();
-    method public String? getScheduleTimezone();
-    method public androidx.appactions.builtintypes.properties.StartDate? getStartDate();
-    method public androidx.appactions.builtintypes.properties.StartTime? getStartTime();
+    method @androidx.appsearch.annotation.Document.DocumentProperty(name="byDay") public java.util.List<androidx.appactions.builtintypes.properties.ByDay> getByDays();
+    method @androidx.appsearch.annotation.Document.LongProperty(name="byMonthDay") public java.util.List<java.lang.Long> getByMonthDays();
+    method @androidx.appsearch.annotation.Document.LongProperty(name="byMonthWeek") public java.util.List<java.lang.Long> getByMonthWeeks();
+    method @androidx.appsearch.annotation.Document.LongProperty(name="byMonth") public java.util.List<java.lang.Long> getByMonths();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.EndDate? getEndDate();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.EndTime? getEndTime();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.ExceptDate? getExceptDate();
+    method @androidx.appsearch.annotation.Document.LongProperty public Long? getRepeatCount();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.RepeatFrequency? getRepeatFrequency();
+    method @androidx.appsearch.annotation.Document.StringProperty public String? getScheduleTimezone();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.StartDate? getStartDate();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.StartTime? getStartTime();
     method public androidx.appactions.builtintypes.types.Schedule.Builder<?> toBuilder();
-    property public abstract java.util.List<androidx.appactions.builtintypes.properties.ByDay> byDays;
-    property public abstract java.util.List<java.lang.Long> byMonthDays;
-    property public abstract java.util.List<java.lang.Long> byMonthWeeks;
-    property public abstract java.util.List<java.lang.Long> byMonths;
-    property public abstract androidx.appactions.builtintypes.properties.EndDate? endDate;
-    property public abstract androidx.appactions.builtintypes.properties.EndTime? endTime;
-    property public abstract androidx.appactions.builtintypes.properties.ExceptDate? exceptDate;
-    property public abstract Long? repeatCount;
-    property public abstract androidx.appactions.builtintypes.properties.RepeatFrequency? repeatFrequency;
-    property public abstract String? scheduleTimezone;
-    property public abstract androidx.appactions.builtintypes.properties.StartDate? startDate;
-    property public abstract androidx.appactions.builtintypes.properties.StartTime? startTime;
+    property @androidx.appsearch.annotation.Document.DocumentProperty(name="byDay") public abstract java.util.List<androidx.appactions.builtintypes.properties.ByDay> byDays;
+    property @androidx.appsearch.annotation.Document.LongProperty(name="byMonthDay") public abstract java.util.List<java.lang.Long> byMonthDays;
+    property @androidx.appsearch.annotation.Document.LongProperty(name="byMonthWeek") public abstract java.util.List<java.lang.Long> byMonthWeeks;
+    property @androidx.appsearch.annotation.Document.LongProperty(name="byMonth") public abstract java.util.List<java.lang.Long> byMonths;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.EndDate? endDate;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.EndTime? endTime;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.ExceptDate? exceptDate;
+    property @androidx.appsearch.annotation.Document.LongProperty public abstract Long? repeatCount;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.RepeatFrequency? repeatFrequency;
+    property @androidx.appsearch.annotation.Document.StringProperty public abstract String? scheduleTimezone;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.StartDate? startDate;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.StartTime? startTime;
     field public static final androidx.appactions.builtintypes.types.Schedule.Companion Companion;
   }
 
@@ -843,13 +907,13 @@
     method public androidx.appactions.builtintypes.types.Schedule.Builder<?> Builder();
   }
 
-  public interface SuccessStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+  @androidx.appsearch.annotation.Document(name="bit:SuccessStatus") public interface SuccessStatus extends androidx.appactions.builtintypes.types.CommonExecutionStatus {
     method public static androidx.appactions.builtintypes.types.SuccessStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.SuccessStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.SuccessStatus.Companion Companion;
   }
 
-  public static interface SuccessStatus.Builder<Self extends androidx.appactions.builtintypes.types.SuccessStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.ExecutionStatus.Builder<Self> {
+  public static interface SuccessStatus.Builder<Self extends androidx.appactions.builtintypes.types.SuccessStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<Self> {
     method public androidx.appactions.builtintypes.types.SuccessStatus build();
   }
 
@@ -857,17 +921,17 @@
     method public androidx.appactions.builtintypes.types.SuccessStatus.Builder<?> Builder();
   }
 
-  public interface Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Thing") public interface Thing {
     method public static androidx.appactions.builtintypes.types.Thing.Builder<?> Builder();
-    method public androidx.appactions.builtintypes.properties.DisambiguatingDescription? getDisambiguatingDescription();
-    method public String? getIdentifier();
-    method public androidx.appactions.builtintypes.properties.Name? getName();
-    method public String? getNamespace();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.DisambiguatingDescription? getDisambiguatingDescription();
+    method @androidx.appsearch.annotation.Document.Id public String? getIdentifier();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.Name? getName();
+    method @androidx.appsearch.annotation.Document.Namespace public String? getNamespace();
     method public androidx.appactions.builtintypes.types.Thing.Builder<?> toBuilder();
-    property public abstract androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription;
-    property public abstract String? identifier;
-    property public abstract androidx.appactions.builtintypes.properties.Name? name;
-    property public abstract String? namespace;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription;
+    property @androidx.appsearch.annotation.Document.Id public abstract String? identifier;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.Name? name;
+    property @androidx.appsearch.annotation.Document.Namespace public abstract String? namespace;
     field public static final androidx.appactions.builtintypes.types.Thing.Companion Companion;
   }
 
@@ -885,7 +949,7 @@
     method public androidx.appactions.builtintypes.types.Thing.Builder<?> Builder();
   }
 
-  public interface Timer extends androidx.appactions.builtintypes.types.Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Timer") public interface Timer extends androidx.appactions.builtintypes.types.Thing {
     method public static androidx.appactions.builtintypes.types.Timer.Builder<?> Builder();
     method public java.time.Duration? getDuration();
     method public androidx.appactions.builtintypes.types.Timer.Builder<?> toBuilder();
@@ -902,7 +966,7 @@
     method public androidx.appactions.builtintypes.types.Timer.Builder<?> Builder();
   }
 
-  public interface UnsupportedOperationStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+  @androidx.appsearch.annotation.Document(name="bit:UnsupportedOperationStatus") public interface UnsupportedOperationStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
     method public static androidx.appactions.builtintypes.types.UnsupportedOperationStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.UnsupportedOperationStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.UnsupportedOperationStatus.Companion Companion;
diff --git a/appactions/builtintypes/builtintypes/api/restricted_current.txt b/appactions/builtintypes/builtintypes/api/restricted_current.txt
index be0a503..fe310a0 100644
--- a/appactions/builtintypes/builtintypes/api/restricted_current.txt
+++ b/appactions/builtintypes/builtintypes/api/restricted_current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.appactions.builtintypes.properties {
 
-  public final class ByDay {
+  @androidx.appsearch.annotation.Document(name="bitprop:ByDay") public final class ByDay {
     ctor public ByDay(androidx.appactions.builtintypes.types.DayOfWeek dayOfWeek);
     ctor public ByDay(String text);
     method public androidx.appactions.builtintypes.types.DayOfWeek? getAsDayOfWeek();
@@ -17,7 +17,7 @@
     method public default R text(String instance);
   }
 
-  public final class DisambiguatingDescription {
+  @androidx.appsearch.annotation.Document(name="bitprop:DisambiguatingDescription") public final class DisambiguatingDescription {
     ctor public DisambiguatingDescription(androidx.appactions.builtintypes.properties.DisambiguatingDescription.CanonicalValue canonicalValue);
     ctor public DisambiguatingDescription(String text);
     method public androidx.appactions.builtintypes.properties.DisambiguatingDescription.CanonicalValue? getAsCanonicalValue();
@@ -38,7 +38,7 @@
     method public default R text(String instance);
   }
 
-  public final class EndDate {
+  @androidx.appsearch.annotation.Document(name="bitprop:EndDate") public final class EndDate {
     ctor public EndDate(java.time.Instant instant);
     ctor public EndDate(java.time.LocalDate date);
     ctor public EndDate(java.time.LocalDateTime localDateTime);
@@ -58,7 +58,7 @@
     method public R orElse();
   }
 
-  public final class EndTime {
+  @androidx.appsearch.annotation.Document(name="bitprop:EndTime") public final class EndTime {
     ctor public EndTime(java.time.Instant instant);
     ctor public EndTime(java.time.LocalDateTime localDateTime);
     ctor public EndTime(java.time.LocalTime time);
@@ -78,7 +78,7 @@
     method public default R time(java.time.LocalTime instance);
   }
 
-  public final class ExceptDate {
+  @androidx.appsearch.annotation.Document(name="bitprop:ExceptDate") public final class ExceptDate {
     ctor public ExceptDate(java.time.Instant instant);
     ctor public ExceptDate(java.time.LocalDate date);
     ctor public ExceptDate(java.time.LocalDateTime localDateTime);
@@ -98,13 +98,13 @@
     method public R orElse();
   }
 
-  public final class Name {
+  @androidx.appsearch.annotation.Document(name="bitprop:Name") public final class Name {
     ctor public Name(String text);
     method public String? getAsText();
     property public final String? asText;
   }
 
-  public final class RepeatFrequency {
+  @androidx.appsearch.annotation.Document(name="bitprop:RepeatFrequency") public final class RepeatFrequency {
     ctor public RepeatFrequency(String text);
     ctor public RepeatFrequency(java.time.Duration duration);
     method public java.time.Duration? getAsDuration();
@@ -120,7 +120,7 @@
     method public default R text(String instance);
   }
 
-  public final class StartDate {
+  @androidx.appsearch.annotation.Document(name="bitprop:StartDate") public final class StartDate {
     ctor public StartDate(java.time.Instant instant);
     ctor public StartDate(java.time.LocalDate date);
     ctor public StartDate(java.time.LocalDateTime localDateTime);
@@ -140,7 +140,7 @@
     method public R orElse();
   }
 
-  public final class StartTime {
+  @androidx.appsearch.annotation.Document(name="bitprop:StartTime") public final class StartTime {
     ctor public StartTime(java.time.Instant instant);
     ctor public StartTime(java.time.LocalDateTime localDateTime);
     ctor public StartTime(java.time.LocalTime time);
@@ -175,6 +175,7 @@
     method public final String? getNamespace();
     method protected abstract String getSelfTypeName();
     method public final int hashCode();
+    method public final Boolean? isAlarmEnabled();
     method public final Builder toBuilder();
     method protected abstract Builder toBuilderWithAdditionalPropertiesOnly();
     method public final String toString();
@@ -182,6 +183,7 @@
     property public final androidx.appactions.builtintypes.types.Schedule? alarmSchedule;
     property public final androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription;
     property public final String? identifier;
+    property public final Boolean? isAlarmEnabled;
     property public final androidx.appactions.builtintypes.properties.Name? name;
     property public final String? namespace;
     property protected abstract String selfTypeName;
@@ -195,6 +197,7 @@
     method protected abstract java.util.Map<java.lang.String,java.lang.Object> getAdditionalProperties();
     method protected abstract String getSelfTypeName();
     method public final int hashCode();
+    method public final Self setAlarmEnabled(Boolean? boolean);
     method public final Self setAlarmSchedule(androidx.appactions.builtintypes.types.Schedule? schedule);
     method public final Self setDisambiguatingDescription(androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription);
     method public final Self setIdentifier(String? text);
@@ -205,6 +208,44 @@
     property protected abstract String selfTypeName;
   }
 
+  public abstract class AbstractCommonExecutionStatus<Self extends androidx.appactions.builtintypes.types.AbstractCommonExecutionStatus<Self, Builder>, Builder extends androidx.appactions.builtintypes.types.AbstractCommonExecutionStatus.Builder<Builder, Self>> implements androidx.appactions.builtintypes.types.CommonExecutionStatus {
+    ctor public AbstractCommonExecutionStatus(androidx.appactions.builtintypes.types.CommonExecutionStatus commonExecutionStatus);
+    method public final boolean equals(Object? other);
+    method protected abstract java.util.Map<java.lang.String,java.lang.Object> getAdditionalProperties();
+    method public final androidx.appactions.builtintypes.properties.DisambiguatingDescription? getDisambiguatingDescription();
+    method public final String? getIdentifier();
+    method public final androidx.appactions.builtintypes.properties.Name? getName();
+    method public final String? getNamespace();
+    method protected abstract String getSelfTypeName();
+    method public final int hashCode();
+    method public final Builder toBuilder();
+    method protected abstract Builder toBuilderWithAdditionalPropertiesOnly();
+    method public final String toString();
+    property protected abstract java.util.Map<java.lang.String,java.lang.Object> additionalProperties;
+    property public final androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription;
+    property public final String? identifier;
+    property public final androidx.appactions.builtintypes.properties.Name? name;
+    property public final String? namespace;
+    property protected abstract String selfTypeName;
+  }
+
+  public abstract static class AbstractCommonExecutionStatus.Builder<Self extends androidx.appactions.builtintypes.types.AbstractCommonExecutionStatus.Builder<Self, Built>, Built extends androidx.appactions.builtintypes.types.AbstractCommonExecutionStatus<Built, Self>> implements androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<Self> {
+    ctor public AbstractCommonExecutionStatus.Builder();
+    method public final Built build();
+    method protected abstract Built buildFromCommonExecutionStatus(androidx.appactions.builtintypes.types.CommonExecutionStatus commonExecutionStatus);
+    method public final boolean equals(Object? other);
+    method protected abstract java.util.Map<java.lang.String,java.lang.Object> getAdditionalProperties();
+    method protected abstract String getSelfTypeName();
+    method public final int hashCode();
+    method public final Self setDisambiguatingDescription(androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription);
+    method public final Self setIdentifier(String? text);
+    method public final Self setName(androidx.appactions.builtintypes.properties.Name? name);
+    method public final Self setNamespace(String? namespace);
+    method public final String toString();
+    property protected abstract java.util.Map<java.lang.String,java.lang.Object> additionalProperties;
+    property protected abstract String selfTypeName;
+  }
+
   public abstract class AbstractExecutionStatus<Self extends androidx.appactions.builtintypes.types.AbstractExecutionStatus<Self, Builder>, Builder extends androidx.appactions.builtintypes.types.AbstractExecutionStatus.Builder<Builder, Self>> implements androidx.appactions.builtintypes.types.ExecutionStatus {
     ctor public AbstractExecutionStatus(androidx.appactions.builtintypes.types.ExecutionStatus executionStatus);
     method public final boolean equals(Object? other);
@@ -367,6 +408,7 @@
     method public final androidx.appactions.builtintypes.properties.Name? getName();
     method public final String? getNamespace();
     method protected abstract String getSelfTypeName();
+    method public final String? getTelephone();
     method public final int hashCode();
     method public final Builder toBuilder();
     method protected abstract Builder toBuilderWithAdditionalPropertiesOnly();
@@ -378,6 +420,7 @@
     property public final androidx.appactions.builtintypes.properties.Name? name;
     property public final String? namespace;
     property protected abstract String selfTypeName;
+    property public final String? telephone;
   }
 
   public abstract static class AbstractPerson.Builder<Self extends androidx.appactions.builtintypes.types.AbstractPerson.Builder<Self, Built>, Built extends androidx.appactions.builtintypes.types.AbstractPerson<Built, Self>> implements androidx.appactions.builtintypes.types.Person.Builder<Self> {
@@ -393,6 +436,7 @@
     method public final Self setIdentifier(String? text);
     method public final Self setName(androidx.appactions.builtintypes.properties.Name? name);
     method public final Self setNamespace(String? namespace);
+    method public final Self setTelephone(String? text);
     method public final String toString();
     property protected abstract java.util.Map<java.lang.String,java.lang.Object> additionalProperties;
     property protected abstract String selfTypeName;
@@ -635,16 +679,19 @@
     property protected abstract String selfTypeName;
   }
 
-  public interface Alarm extends androidx.appactions.builtintypes.types.Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Alarm") public interface Alarm extends androidx.appactions.builtintypes.types.Thing {
     method public static androidx.appactions.builtintypes.types.Alarm.Builder<?> Builder();
-    method public androidx.appactions.builtintypes.types.Schedule? getAlarmSchedule();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.types.Schedule? getAlarmSchedule();
+    method @androidx.appsearch.annotation.Document.BooleanProperty public Boolean? isAlarmEnabled();
     method public androidx.appactions.builtintypes.types.Alarm.Builder<?> toBuilder();
-    property public abstract androidx.appactions.builtintypes.types.Schedule? alarmSchedule;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.types.Schedule? alarmSchedule;
+    property @androidx.appsearch.annotation.Document.BooleanProperty public abstract Boolean? isAlarmEnabled;
     field public static final androidx.appactions.builtintypes.types.Alarm.Companion Companion;
   }
 
   public static interface Alarm.Builder<Self extends androidx.appactions.builtintypes.types.Alarm.Builder<Self>> extends androidx.appactions.builtintypes.types.Thing.Builder<Self> {
     method public androidx.appactions.builtintypes.types.Alarm build();
+    method public Self setAlarmEnabled(Boolean? boolean);
     method public Self setAlarmSchedule(androidx.appactions.builtintypes.types.Schedule? schedule);
     method public default Self setDisambiguatingDescription(androidx.appactions.builtintypes.types.Alarm.DisambiguatingDescriptionValue canonicalValue);
   }
@@ -663,6 +710,20 @@
   public static final class Alarm.DisambiguatingDescriptionValue.Companion {
   }
 
+  @androidx.appsearch.annotation.Document(name="bit:CommonExecutionStatus") public interface CommonExecutionStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+    method public static androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<?> Builder();
+    method public androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<?> toBuilder();
+    field public static final androidx.appactions.builtintypes.types.CommonExecutionStatus.Companion Companion;
+  }
+
+  public static interface CommonExecutionStatus.Builder<Self extends androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.ExecutionStatus.Builder<Self> {
+    method public androidx.appactions.builtintypes.types.CommonExecutionStatus build();
+  }
+
+  public static final class CommonExecutionStatus.Companion {
+    method public androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<?> Builder();
+  }
+
   public final class DayOfWeek {
     method public String getCanonicalUrl();
     method public <R> R mapWhen(androidx.appactions.builtintypes.types.DayOfWeek.Mapper<R> mapper);
@@ -693,7 +754,7 @@
     method public default R wednesday();
   }
 
-  public interface ExecutionStatus extends androidx.appactions.builtintypes.types.Intangible {
+  @androidx.appsearch.annotation.Document(name="bit:ExecutionStatus") public interface ExecutionStatus extends androidx.appactions.builtintypes.types.Intangible {
     method public static androidx.appactions.builtintypes.types.ExecutionStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.ExecutionStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.ExecutionStatus.Companion Companion;
@@ -707,13 +768,13 @@
     method public androidx.appactions.builtintypes.types.ExecutionStatus.Builder<?> Builder();
   }
 
-  public interface GenericErrorStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+  @androidx.appsearch.annotation.Document(name="bit:GenericErrorStatus") public interface GenericErrorStatus extends androidx.appactions.builtintypes.types.CommonExecutionStatus {
     method public static androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.GenericErrorStatus.Companion Companion;
   }
 
-  public static interface GenericErrorStatus.Builder<Self extends androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.ExecutionStatus.Builder<Self> {
+  public static interface GenericErrorStatus.Builder<Self extends androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<Self> {
     method public androidx.appactions.builtintypes.types.GenericErrorStatus build();
   }
 
@@ -721,7 +782,7 @@
     method public androidx.appactions.builtintypes.types.GenericErrorStatus.Builder<?> Builder();
   }
 
-  public interface Intangible extends androidx.appactions.builtintypes.types.Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Intangible") public interface Intangible extends androidx.appactions.builtintypes.types.Thing {
     method public static androidx.appactions.builtintypes.types.Intangible.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.Intangible.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.Intangible.Companion Companion;
@@ -735,7 +796,7 @@
     method public androidx.appactions.builtintypes.types.Intangible.Builder<?> Builder();
   }
 
-  public interface ObjectCreationLimitReachedStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+  @androidx.appsearch.annotation.Document(name="bit:ObjectCreationLimitReachedStatus") public interface ObjectCreationLimitReachedStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
     method public static androidx.appactions.builtintypes.types.ObjectCreationLimitReachedStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.ObjectCreationLimitReachedStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.ObjectCreationLimitReachedStatus.Companion Companion;
@@ -749,50 +810,53 @@
     method public androidx.appactions.builtintypes.types.ObjectCreationLimitReachedStatus.Builder<?> Builder();
   }
 
-  public interface Person extends androidx.appactions.builtintypes.types.Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Person") public interface Person extends androidx.appactions.builtintypes.types.Thing {
     method public static androidx.appactions.builtintypes.types.Person.Builder<?> Builder();
-    method public String? getEmail();
+    method @androidx.appsearch.annotation.Document.StringProperty public String? getEmail();
+    method @androidx.appsearch.annotation.Document.StringProperty public String? getTelephone();
     method public androidx.appactions.builtintypes.types.Person.Builder<?> toBuilder();
-    property public abstract String? email;
+    property @androidx.appsearch.annotation.Document.StringProperty public abstract String? email;
+    property @androidx.appsearch.annotation.Document.StringProperty public abstract String? telephone;
     field public static final androidx.appactions.builtintypes.types.Person.Companion Companion;
   }
 
   public static interface Person.Builder<Self extends androidx.appactions.builtintypes.types.Person.Builder<Self>> extends androidx.appactions.builtintypes.types.Thing.Builder<Self> {
     method public androidx.appactions.builtintypes.types.Person build();
     method public Self setEmail(String? text);
+    method public Self setTelephone(String? text);
   }
 
   public static final class Person.Companion {
     method public androidx.appactions.builtintypes.types.Person.Builder<?> Builder();
   }
 
-  public interface Schedule extends androidx.appactions.builtintypes.types.Intangible {
+  @androidx.appsearch.annotation.Document(name="bit:Schedule") public interface Schedule extends androidx.appactions.builtintypes.types.Intangible {
     method public static androidx.appactions.builtintypes.types.Schedule.Builder<?> Builder();
-    method public java.util.List<androidx.appactions.builtintypes.properties.ByDay> getByDays();
-    method public java.util.List<java.lang.Long> getByMonthDays();
-    method public java.util.List<java.lang.Long> getByMonthWeeks();
-    method public java.util.List<java.lang.Long> getByMonths();
-    method public androidx.appactions.builtintypes.properties.EndDate? getEndDate();
-    method public androidx.appactions.builtintypes.properties.EndTime? getEndTime();
-    method public androidx.appactions.builtintypes.properties.ExceptDate? getExceptDate();
-    method public Long? getRepeatCount();
-    method public androidx.appactions.builtintypes.properties.RepeatFrequency? getRepeatFrequency();
-    method public String? getScheduleTimezone();
-    method public androidx.appactions.builtintypes.properties.StartDate? getStartDate();
-    method public androidx.appactions.builtintypes.properties.StartTime? getStartTime();
+    method @androidx.appsearch.annotation.Document.DocumentProperty(name="byDay") public java.util.List<androidx.appactions.builtintypes.properties.ByDay> getByDays();
+    method @androidx.appsearch.annotation.Document.LongProperty(name="byMonthDay") public java.util.List<java.lang.Long> getByMonthDays();
+    method @androidx.appsearch.annotation.Document.LongProperty(name="byMonthWeek") public java.util.List<java.lang.Long> getByMonthWeeks();
+    method @androidx.appsearch.annotation.Document.LongProperty(name="byMonth") public java.util.List<java.lang.Long> getByMonths();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.EndDate? getEndDate();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.EndTime? getEndTime();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.ExceptDate? getExceptDate();
+    method @androidx.appsearch.annotation.Document.LongProperty public Long? getRepeatCount();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.RepeatFrequency? getRepeatFrequency();
+    method @androidx.appsearch.annotation.Document.StringProperty public String? getScheduleTimezone();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.StartDate? getStartDate();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.StartTime? getStartTime();
     method public androidx.appactions.builtintypes.types.Schedule.Builder<?> toBuilder();
-    property public abstract java.util.List<androidx.appactions.builtintypes.properties.ByDay> byDays;
-    property public abstract java.util.List<java.lang.Long> byMonthDays;
-    property public abstract java.util.List<java.lang.Long> byMonthWeeks;
-    property public abstract java.util.List<java.lang.Long> byMonths;
-    property public abstract androidx.appactions.builtintypes.properties.EndDate? endDate;
-    property public abstract androidx.appactions.builtintypes.properties.EndTime? endTime;
-    property public abstract androidx.appactions.builtintypes.properties.ExceptDate? exceptDate;
-    property public abstract Long? repeatCount;
-    property public abstract androidx.appactions.builtintypes.properties.RepeatFrequency? repeatFrequency;
-    property public abstract String? scheduleTimezone;
-    property public abstract androidx.appactions.builtintypes.properties.StartDate? startDate;
-    property public abstract androidx.appactions.builtintypes.properties.StartTime? startTime;
+    property @androidx.appsearch.annotation.Document.DocumentProperty(name="byDay") public abstract java.util.List<androidx.appactions.builtintypes.properties.ByDay> byDays;
+    property @androidx.appsearch.annotation.Document.LongProperty(name="byMonthDay") public abstract java.util.List<java.lang.Long> byMonthDays;
+    property @androidx.appsearch.annotation.Document.LongProperty(name="byMonthWeek") public abstract java.util.List<java.lang.Long> byMonthWeeks;
+    property @androidx.appsearch.annotation.Document.LongProperty(name="byMonth") public abstract java.util.List<java.lang.Long> byMonths;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.EndDate? endDate;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.EndTime? endTime;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.ExceptDate? exceptDate;
+    property @androidx.appsearch.annotation.Document.LongProperty public abstract Long? repeatCount;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.RepeatFrequency? repeatFrequency;
+    property @androidx.appsearch.annotation.Document.StringProperty public abstract String? scheduleTimezone;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.StartDate? startDate;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.StartTime? startTime;
     field public static final androidx.appactions.builtintypes.types.Schedule.Companion Companion;
   }
 
@@ -843,13 +907,13 @@
     method public androidx.appactions.builtintypes.types.Schedule.Builder<?> Builder();
   }
 
-  public interface SuccessStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+  @androidx.appsearch.annotation.Document(name="bit:SuccessStatus") public interface SuccessStatus extends androidx.appactions.builtintypes.types.CommonExecutionStatus {
     method public static androidx.appactions.builtintypes.types.SuccessStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.SuccessStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.SuccessStatus.Companion Companion;
   }
 
-  public static interface SuccessStatus.Builder<Self extends androidx.appactions.builtintypes.types.SuccessStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.ExecutionStatus.Builder<Self> {
+  public static interface SuccessStatus.Builder<Self extends androidx.appactions.builtintypes.types.SuccessStatus.Builder<Self>> extends androidx.appactions.builtintypes.types.CommonExecutionStatus.Builder<Self> {
     method public androidx.appactions.builtintypes.types.SuccessStatus build();
   }
 
@@ -857,17 +921,17 @@
     method public androidx.appactions.builtintypes.types.SuccessStatus.Builder<?> Builder();
   }
 
-  public interface Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Thing") public interface Thing {
     method public static androidx.appactions.builtintypes.types.Thing.Builder<?> Builder();
-    method public androidx.appactions.builtintypes.properties.DisambiguatingDescription? getDisambiguatingDescription();
-    method public String? getIdentifier();
-    method public androidx.appactions.builtintypes.properties.Name? getName();
-    method public String? getNamespace();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.DisambiguatingDescription? getDisambiguatingDescription();
+    method @androidx.appsearch.annotation.Document.Id public String? getIdentifier();
+    method @androidx.appsearch.annotation.Document.DocumentProperty public androidx.appactions.builtintypes.properties.Name? getName();
+    method @androidx.appsearch.annotation.Document.Namespace public String? getNamespace();
     method public androidx.appactions.builtintypes.types.Thing.Builder<?> toBuilder();
-    property public abstract androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription;
-    property public abstract String? identifier;
-    property public abstract androidx.appactions.builtintypes.properties.Name? name;
-    property public abstract String? namespace;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.DisambiguatingDescription? disambiguatingDescription;
+    property @androidx.appsearch.annotation.Document.Id public abstract String? identifier;
+    property @androidx.appsearch.annotation.Document.DocumentProperty public abstract androidx.appactions.builtintypes.properties.Name? name;
+    property @androidx.appsearch.annotation.Document.Namespace public abstract String? namespace;
     field public static final androidx.appactions.builtintypes.types.Thing.Companion Companion;
   }
 
@@ -885,7 +949,7 @@
     method public androidx.appactions.builtintypes.types.Thing.Builder<?> Builder();
   }
 
-  public interface Timer extends androidx.appactions.builtintypes.types.Thing {
+  @androidx.appsearch.annotation.Document(name="bit:Timer") public interface Timer extends androidx.appactions.builtintypes.types.Thing {
     method public static androidx.appactions.builtintypes.types.Timer.Builder<?> Builder();
     method public java.time.Duration? getDuration();
     method public androidx.appactions.builtintypes.types.Timer.Builder<?> toBuilder();
@@ -902,7 +966,7 @@
     method public androidx.appactions.builtintypes.types.Timer.Builder<?> Builder();
   }
 
-  public interface UnsupportedOperationStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
+  @androidx.appsearch.annotation.Document(name="bit:UnsupportedOperationStatus") public interface UnsupportedOperationStatus extends androidx.appactions.builtintypes.types.ExecutionStatus {
     method public static androidx.appactions.builtintypes.types.UnsupportedOperationStatus.Builder<?> Builder();
     method public androidx.appactions.builtintypes.types.UnsupportedOperationStatus.Builder<?> toBuilder();
     field public static final androidx.appactions.builtintypes.types.UnsupportedOperationStatus.Companion Companion;
diff --git a/appactions/builtintypes/builtintypes/build.gradle b/appactions/builtintypes/builtintypes/build.gradle
index 419d53b..5a4fc33 100644
--- a/appactions/builtintypes/builtintypes/build.gradle
+++ b/appactions/builtintypes/builtintypes/build.gradle
@@ -25,6 +25,7 @@
 
 dependencies {
     api(libs.kotlinStdlib)
+    api(project(":appsearch:appsearch"))
 
     testImplementation(libs.junit)
     testImplementation(libs.truth)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ByDay.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ByDay.kt
index 08b3cfa..ec6ac9c 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ByDay.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ByDay.kt
@@ -14,6 +14,7 @@
 package androidx.appactions.builtintypes.properties
 
 import androidx.appactions.builtintypes.types.DayOfWeek
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -25,7 +26,7 @@
 /**
  * Defines the day(s) of the week on which a recurring Event takes place.
  *
- * See http://schema.org/byDay for context.
+ * See https://schema.org/byDay for context.
  *
  * Holds one of:
  * * Text i.e. [String]
@@ -33,10 +34,11 @@
  *
  * May hold more types over time.
  */
+@Document(name = "bitprop:ByDay")
 public class ByDay
 internal constructor(
   /** The [String] variant, or null if constructed using a different variant. */
-  @get:JvmName("asText") public val asText: String? = null,
+  @get:JvmName("asText") @Document.StringProperty public val asText: String? = null,
   /** The [DayOfWeek] variant, or null if constructed using a different variant. */
   @get:JvmName("asDayOfWeek") public val asDayOfWeek: DayOfWeek? = null,
   /**
@@ -45,7 +47,7 @@
    * Every AppSearch document needs an identifier. Since property wrappers are only meant to be used
    * at nested levels, this is internal and will always be an empty string.
    */
-  internal val identifier: String = "",
+  @Document.Id internal val identifier: String = "",
 ) {
   /** Constructor for the [String] variant. */
   public constructor(text: String) : this(asText = text)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/DisambiguatingDescription.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/DisambiguatingDescription.kt
index 9380e47..0cd3381 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/DisambiguatingDescription.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/DisambiguatingDescription.kt
@@ -13,6 +13,7 @@
 // limitations under the License.
 package androidx.appactions.builtintypes.properties
 
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -26,7 +27,7 @@
  * similar items. Information from other properties (in particular, name) may be necessary for the
  * description to be useful for disambiguation.
  *
- * See http://schema.org/disambiguatingDescription for context.
+ * See https://schema.org/disambiguatingDescription for context.
  *
  * Holds one of:
  * * Text i.e. [String]
@@ -34,10 +35,11 @@
  *
  * May hold more types over time.
  */
+@Document(name = "bitprop:DisambiguatingDescription")
 public class DisambiguatingDescription
 internal constructor(
   /** The [String] variant, or null if constructed using a different variant. */
-  @get:JvmName("asText") public val asText: String? = null,
+  @get:JvmName("asText") @Document.StringProperty public val asText: String? = null,
   /** The [CanonicalValue] variant, or null if constructed using a different variant. */
   @get:JvmName("asCanonicalValue") public val asCanonicalValue: CanonicalValue? = null,
   /**
@@ -46,7 +48,7 @@
    * Every AppSearch document needs an identifier. Since property wrappers are only meant to be used
    * at nested levels, this is internal and will always be an empty string.
    */
-  internal val identifier: String = "",
+  @Document.Id internal val identifier: String = "",
 ) {
   /** Constructor for the [String] variant. */
   public constructor(text: String) : this(asText = text)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt
index b795c26..13da6b6 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt
@@ -13,6 +13,7 @@
 // limitations under the License.
 package androidx.appactions.builtintypes.properties
 
+import androidx.appsearch.`annotation`.Document
 import java.time.Instant
 import java.time.LocalDate
 import java.time.LocalDateTime
@@ -27,7 +28,7 @@
 /**
  * The end date and time of the item.
  *
- * See http://schema.org/endDate for context.
+ * See https://schema.org/endDate for context.
  *
  * Holds one of:
  * * Date i.e. [LocalDate]
@@ -36,6 +37,7 @@
  *
  * May hold more types over time.
  */
+@Document(name = "bitprop:EndDate")
 public class EndDate
 internal constructor(
   /** The [LocalDate] variant, or null if constructed using a different variant. */
@@ -50,7 +52,7 @@
    * Every AppSearch document needs an identifier. Since property wrappers are only meant to be used
    * at nested levels, this is internal and will always be an empty string.
    */
-  internal val identifier: String = "",
+  @Document.Id internal val identifier: String = "",
 ) {
   /** Constructor for the [LocalDate] variant. */
   public constructor(date: LocalDate) : this(asDate = date)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndTime.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndTime.kt
index 2785f5b..067cf93 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndTime.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndTime.kt
@@ -13,6 +13,7 @@
 // limitations under the License.
 package androidx.appactions.builtintypes.properties
 
+import androidx.appsearch.`annotation`.Document
 import java.time.Instant
 import java.time.LocalDateTime
 import java.time.LocalTime
@@ -32,7 +33,7 @@
  * wrote a book from January to *December*. For media, including audio and video, it's the time
  * offset of the end of a clip within a larger file.
  *
- * See http://schema.org/endTime for context.
+ * See https://schema.org/endTime for context.
  *
  * Holds one of:
  * * Time i.e. [LocalTime]
@@ -41,6 +42,7 @@
  *
  * May hold more types over time.
  */
+@Document(name = "bitprop:EndTime")
 public class EndTime
 internal constructor(
   /** The [LocalTime] variant, or null if constructed using a different variant. */
@@ -55,7 +57,7 @@
    * Every AppSearch document needs an identifier. Since property wrappers are only meant to be used
    * at nested levels, this is internal and will always be an empty string.
    */
-  internal val identifier: String = "",
+  @Document.Id internal val identifier: String = "",
 ) {
   /** Constructor for the [LocalTime] variant. */
   public constructor(time: LocalTime) : this(asTime = time)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ExceptDate.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ExceptDate.kt
index c0dd8a3..a710a7f 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ExceptDate.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ExceptDate.kt
@@ -13,6 +13,7 @@
 // limitations under the License.
 package androidx.appactions.builtintypes.properties
 
+import androidx.appsearch.`annotation`.Document
 import java.time.Instant
 import java.time.LocalDate
 import java.time.LocalDateTime
@@ -32,7 +33,7 @@
  * that 24 hour period should be excluded from the schedule. This allows a whole day to be excluded
  * from the schedule without having to itemise every scheduled event.
  *
- * See http://schema.org/exceptDate for context.
+ * See https://schema.org/exceptDate for context.
  *
  * Holds one of:
  * * Date i.e. [LocalDate]
@@ -41,6 +42,7 @@
  *
  * May hold more types over time.
  */
+@Document(name = "bitprop:ExceptDate")
 public class ExceptDate
 internal constructor(
   /** The [LocalDate] variant, or null if constructed using a different variant. */
@@ -55,7 +57,7 @@
    * Every AppSearch document needs an identifier. Since property wrappers are only meant to be used
    * at nested levels, this is internal and will always be an empty string.
    */
-  internal val identifier: String = "",
+  @Document.Id internal val identifier: String = "",
 ) {
   /** Constructor for the [LocalDate] variant. */
   public constructor(date: LocalDate) : this(asDate = date)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/Name.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/Name.kt
index d729dc8..a82e5b5 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/Name.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/Name.kt
@@ -13,6 +13,7 @@
 // limitations under the License.
 package androidx.appactions.builtintypes.properties
 
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -24,24 +25,25 @@
 /**
  * The name of the item.
  *
- * See http://schema.org/name for context.
+ * See https://schema.org/name for context.
  *
  * Holds one of:
  * * Text i.e. [String]
  *
  * May hold more types over time.
  */
+@Document(name = "bitprop:Name")
 public class Name
 internal constructor(
   /** The [String] variant, or null if constructed using a different variant. */
-  @get:JvmName("asText") public val asText: String? = null,
+  @get:JvmName("asText") @Document.StringProperty public val asText: String? = null,
   /**
    * The AppSearch document's identifier.
    *
    * Every AppSearch document needs an identifier. Since property wrappers are only meant to be used
    * at nested levels, this is internal and will always be an empty string.
    */
-  internal val identifier: String = "",
+  @Document.Id internal val identifier: String = "",
 ) {
   /** Constructor for the [String] variant. */
   public constructor(text: String) : this(asText = text)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/RepeatFrequency.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/RepeatFrequency.kt
index e04c423..c3e1e13 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/RepeatFrequency.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/RepeatFrequency.kt
@@ -13,6 +13,7 @@
 // limitations under the License.
 package androidx.appactions.builtintypes.properties
 
+import androidx.appsearch.`annotation`.Document
 import java.time.Duration
 import java.util.Objects
 import kotlin.Any
@@ -26,7 +27,7 @@
  * Defines the frequency at which `Event`s will occur according to a schedule `Schedule`. The
  * intervals between events should be defined as a `Duration` of time.
  *
- * See http://schema.org/repeatFrequency for context.
+ * See https://schema.org/repeatFrequency for context.
  *
  * Holds one of:
  * * [Duration]
@@ -34,19 +35,20 @@
  *
  * May hold more types over time.
  */
+@Document(name = "bitprop:RepeatFrequency")
 public class RepeatFrequency
 internal constructor(
   /** The [Duration] variant, or null if constructed using a different variant. */
   @get:JvmName("asDuration") public val asDuration: Duration? = null,
   /** The [String] variant, or null if constructed using a different variant. */
-  @get:JvmName("asText") public val asText: String? = null,
+  @get:JvmName("asText") @Document.StringProperty public val asText: String? = null,
   /**
    * The AppSearch document's identifier.
    *
    * Every AppSearch document needs an identifier. Since property wrappers are only meant to be used
    * at nested levels, this is internal and will always be an empty string.
    */
-  internal val identifier: String = "",
+  @Document.Id internal val identifier: String = "",
 ) {
   /** Constructor for the [Duration] variant. */
   public constructor(duration: Duration) : this(asDuration = duration)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt
index 4eaedc3..e4e1784b 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt
@@ -13,6 +13,7 @@
 // limitations under the License.
 package androidx.appactions.builtintypes.properties
 
+import androidx.appsearch.`annotation`.Document
 import java.time.Instant
 import java.time.LocalDate
 import java.time.LocalDateTime
@@ -27,7 +28,7 @@
 /**
  * The start date and time of the item.
  *
- * See http://schema.org/startDate for context.
+ * See https://schema.org/startDate for context.
  *
  * Holds one of:
  * * Date i.e. [LocalDate]
@@ -36,6 +37,7 @@
  *
  * May hold more types over time.
  */
+@Document(name = "bitprop:StartDate")
 public class StartDate
 internal constructor(
   /** The [LocalDate] variant, or null if constructed using a different variant. */
@@ -50,7 +52,7 @@
    * Every AppSearch document needs an identifier. Since property wrappers are only meant to be used
    * at nested levels, this is internal and will always be an empty string.
    */
-  internal val identifier: String = "",
+  @Document.Id internal val identifier: String = "",
 ) {
   /** Constructor for the [LocalDate] variant. */
   public constructor(date: LocalDate) : this(asDate = date)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartTime.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartTime.kt
index bb0ce0c..46c9be6 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartTime.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartTime.kt
@@ -13,6 +13,7 @@
 // limitations under the License.
 package androidx.appactions.builtintypes.properties
 
+import androidx.appsearch.`annotation`.Document
 import java.time.Instant
 import java.time.LocalDateTime
 import java.time.LocalTime
@@ -32,7 +33,7 @@
  * John wrote a book from *January* to December. For media, including audio and video, it's the time
  * offset of the start of a clip within a larger file.
  *
- * See http://schema.org/startTime for context.
+ * See https://schema.org/startTime for context.
  *
  * Holds one of:
  * * Time i.e. [LocalTime]
@@ -41,6 +42,7 @@
  *
  * May hold more types over time.
  */
+@Document(name = "bitprop:StartTime")
 public class StartTime
 internal constructor(
   /** The [LocalTime] variant, or null if constructed using a different variant. */
@@ -55,7 +57,7 @@
    * Every AppSearch document needs an identifier. Since property wrappers are only meant to be used
    * at nested levels, this is internal and will always be an empty string.
    */
-  internal val identifier: String = "",
+  @Document.Id internal val identifier: String = "",
 ) {
   /** Constructor for the [LocalTime] variant. */
   public constructor(time: LocalTime) : this(asTime = time)
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt
index a7fc228..28f7bd0 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -33,18 +34,26 @@
 /**
  * An alarm set to go off at a specified schedule.
  *
- * See http://schema.googleapis.com/Alarm for context.
+ * See https://schema.googleapis.com/Alarm for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractAlarm] if you need to extend this type.
  */
+@Document(name = "bit:Alarm")
 public interface Alarm : Thing {
   /**
    * Associates an Alarm with a Schedule.
    *
-   * See http://schema.googleapis.com/alarmSchedule for more context.
+   * See https://schema.googleapis.com/alarmSchedule for more context.
    */
-  public val alarmSchedule: Schedule?
+  @get:Document.DocumentProperty public val alarmSchedule: Schedule?
+
+  /**
+   * Specifies if the alarm enabled or disabled.
+   *
+   * See http://schema.googleapis.com/isAlarmEnabled for more context.
+   */
+  @get:Document.BooleanProperty @get:Suppress("AutoBoxing") public val isAlarmEnabled: Boolean?
 
   /** Converts this [Alarm] to its builder with all the properties copied over. */
   public override fun toBuilder(): Builder<*>
@@ -67,6 +76,9 @@
     /** Sets the `alarmSchedule`. */
     public fun setAlarmSchedule(schedule: Schedule?): Self
 
+    /** Sets the `isAlarmEnabled`. */
+    public fun setAlarmEnabled(@Suppress("AutoBoxing") boolean: Boolean?): Self
+
     /** Sets the `disambiguatingDescription` to a canonical [DisambiguatingDescriptionValue]. */
     public fun setDisambiguatingDescription(canonicalValue: DisambiguatingDescriptionValue): Self =
       setDisambiguatingDescription(DisambiguatingDescription(canonicalValue))
@@ -134,6 +146,7 @@
 internal constructor(
   public final override val namespace: String?,
   public final override val alarmSchedule: Schedule?,
+  @get:Suppress("AutoBoxing") public final override val isAlarmEnabled: Boolean?,
   public final override val disambiguatingDescription: DisambiguatingDescription?,
   public final override val identifier: String?,
   public final override val name: Name?,
@@ -158,6 +171,7 @@
   ) : this(
     alarm.namespace,
     alarm.alarmSchedule,
+    alarm.isAlarmEnabled,
     alarm.disambiguatingDescription,
     alarm.identifier,
     alarm.name
@@ -170,6 +184,7 @@
     toBuilderWithAdditionalPropertiesOnly()
       .setNamespace(namespace)
       .setAlarmSchedule(alarmSchedule)
+      .setAlarmEnabled(isAlarmEnabled)
       .setDisambiguatingDescription(disambiguatingDescription)
       .setIdentifier(identifier)
       .setName(name)
@@ -178,22 +193,24 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (alarmSchedule != other.alarmSchedule) return false
+    if (isAlarmEnabled != other.isAlarmEnabled) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
     Objects.hash(
+      namespace,
       alarmSchedule,
+      isAlarmEnabled,
       disambiguatingDescription,
       identifier,
       name,
-      namespace,
       additionalProperties
     )
 
@@ -205,6 +222,9 @@
     if (alarmSchedule != null) {
       attributes["alarmSchedule"] = alarmSchedule.toString()
     }
+    if (isAlarmEnabled != null) {
+      attributes["isAlarmEnabled"] = isAlarmEnabled.toString()
+    }
     if (disambiguatingDescription != null) {
       attributes["disambiguatingDescription"] =
         disambiguatingDescription.toString(includeWrapperName = false)
@@ -292,6 +312,8 @@
 
     private var alarmSchedule: Schedule? = null
 
+    @get:Suppress("AutoBoxing") private var isAlarmEnabled: Boolean? = null
+
     private var disambiguatingDescription: DisambiguatingDescription? = null
 
     private var identifier: String? = null
@@ -313,6 +335,7 @@
         AlarmImpl(
           namespace,
           alarmSchedule,
+          isAlarmEnabled,
           disambiguatingDescription,
           identifier,
           name
@@ -329,6 +352,11 @@
       return this as Self
     }
 
+    public final override fun setAlarmEnabled(@Suppress("AutoBoxing") boolean: Boolean?): Self {
+      this.isAlarmEnabled = boolean
+      return this as Self
+    }
+
     public final override fun setDisambiguatingDescription(
       disambiguatingDescription: DisambiguatingDescription?
     ): Self {
@@ -351,11 +379,12 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (alarmSchedule != other.alarmSchedule) return false
+      if (isAlarmEnabled != other.isAlarmEnabled) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
@@ -363,11 +392,12 @@
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
       Objects.hash(
+        namespace,
         alarmSchedule,
+        isAlarmEnabled,
         disambiguatingDescription,
         identifier,
         name,
-        namespace,
         additionalProperties
       )
 
@@ -380,6 +410,9 @@
       if (alarmSchedule != null) {
         attributes["alarmSchedule"] = alarmSchedule!!.toString()
       }
+      if (isAlarmEnabled != null) {
+        attributes["isAlarmEnabled"] = isAlarmEnabled!!.toString()
+      }
       if (disambiguatingDescription != null) {
         attributes["disambiguatingDescription"] =
           disambiguatingDescription!!.toString(includeWrapperName = false)
@@ -408,10 +441,11 @@
   public constructor(
     namespace: String?,
     alarmSchedule: Schedule?,
+    isAlarmEnabled: Boolean?,
     disambiguatingDescription: DisambiguatingDescription?,
     identifier: String?,
     name: Name?,
-  ) : super(namespace, alarmSchedule, disambiguatingDescription, identifier, name)
+  ) : super(namespace, alarmSchedule, isAlarmEnabled, disambiguatingDescription, identifier, name)
 
   public constructor(alarm: Alarm) : super(alarm)
 
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/CommonExecutionStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/CommonExecutionStatus.kt
new file mode 100644
index 0000000..a2343f4
--- /dev/null
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/CommonExecutionStatus.kt
@@ -0,0 +1,378 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package androidx.appactions.builtintypes.types
+
+import androidx.appactions.builtintypes.properties.DisambiguatingDescription
+import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
+import java.util.Objects
+import kotlin.Any
+import kotlin.Boolean
+import kotlin.Int
+import kotlin.String
+import kotlin.Suppress
+import kotlin.collections.Map
+import kotlin.collections.emptyMap
+import kotlin.collections.joinToString
+import kotlin.collections.map
+import kotlin.collections.mutableMapOf
+import kotlin.collections.plusAssign
+import kotlin.jvm.JvmStatic
+
+/**
+ * A parent type that serves as the umbrella for a number of canonical execution statuses that apply
+ * to the vast majority of tasks.
+ *
+ * Prefer one of the subtypes in most contexts to represent a specific type of common status e.g.
+ * `SuccessStatus`.
+ *
+ * See https://schema.googleapis.com/CommonExecutionStatus for context.
+ *
+ * Should not be directly implemented. More properties may be added over time. Instead consider
+ * using [Companion.Builder] or see [AbstractCommonExecutionStatus] if you need to extend this type.
+ */
+@Document(name = "bit:CommonExecutionStatus")
+public interface CommonExecutionStatus : ExecutionStatus {
+  /** Converts this [CommonExecutionStatus] to its builder with all the properties copied over. */
+  public override fun toBuilder(): Builder<*>
+
+  public companion object {
+    /** Returns a default implementation of [Builder] with no properties set. */
+    @JvmStatic public fun Builder(): Builder<*> = CommonExecutionStatusImpl.Builder()
+  }
+
+  /**
+   * Builder for [CommonExecutionStatus].
+   *
+   * Should not be directly implemented. More methods may be added over time. See
+   * [AbstractCommonExecutionStatus.Builder] if you need to extend this builder.
+   */
+  public interface Builder<Self : Builder<Self>> : ExecutionStatus.Builder<Self> {
+    /** Returns a built [CommonExecutionStatus]. */
+    public override fun build(): CommonExecutionStatus
+  }
+}
+
+/**
+ * An abstract implementation of [CommonExecutionStatus].
+ *
+ * Allows for extension like:
+ * ```kt
+ * class MyCommonExecutionStatus internal constructor(
+ *   commonExecutionStatus: CommonExecutionStatus,
+ *   val foo: String,
+ *   val bars: List<Int>,
+ * ) : AbstractCommonExecutionStatus<
+ *   MyCommonExecutionStatus,
+ *   MyCommonExecutionStatus.Builder
+ * >(commonExecutionStatus) {
+ *
+ *   override val selfTypeName =
+ *     "MyCommonExecutionStatus"
+ *
+ *   override val additionalProperties: Map<String, Any?>
+ *     get() = mapOf("foo" to foo, "bars" to bars)
+ *
+ *   override fun toBuilderWithAdditionalPropertiesOnly(): Builder {
+ *     return Builder()
+ *       .setFoo(foo)
+ *       .addBars(bars)
+ *   }
+ *
+ *   class Builder :
+ *     AbstractCommonExecutionStatus.Builder<
+ *       Builder,
+ *       MyCommonExecutionStatus> {...}
+ * }
+ * ```
+ *
+ * Also see [AbstractCommonExecutionStatus.Builder].
+ */
+@Suppress("UNCHECKED_CAST")
+public abstract class AbstractCommonExecutionStatus<
+  Self : AbstractCommonExecutionStatus<Self, Builder>,
+  Builder : AbstractCommonExecutionStatus.Builder<Builder, Self>>
+internal constructor(
+  public final override val namespace: String?,
+  public final override val disambiguatingDescription: DisambiguatingDescription?,
+  public final override val identifier: String?,
+  public final override val name: Name?,
+) : CommonExecutionStatus {
+  /**
+   * Human readable name for the concrete [Self] class.
+   *
+   * Used in the [toString] output.
+   */
+  protected abstract val selfTypeName: String
+
+  /**
+   * The additional properties that exist on the concrete [Self] class.
+   *
+   * Used for equality comparison and computing the hash code.
+   */
+  protected abstract val additionalProperties: Map<String, Any?>
+
+  /**
+   * A copy-constructor that copies over properties from another [CommonExecutionStatus] instance.
+   */
+  public constructor(
+    commonExecutionStatus: CommonExecutionStatus
+  ) : this(
+    commonExecutionStatus.namespace,
+    commonExecutionStatus.disambiguatingDescription,
+    commonExecutionStatus.identifier,
+    commonExecutionStatus.name
+  )
+
+  /**
+   * Returns a concrete [Builder] with the additional, non-[CommonExecutionStatus] properties copied
+   * over.
+   */
+  protected abstract fun toBuilderWithAdditionalPropertiesOnly(): Builder
+
+  public final override fun toBuilder(): Builder =
+    toBuilderWithAdditionalPropertiesOnly()
+      .setNamespace(namespace)
+      .setDisambiguatingDescription(disambiguatingDescription)
+      .setIdentifier(identifier)
+      .setName(name)
+
+  public final override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other == null || this::class.java != other::class.java) return false
+    other as Self
+    if (namespace != other.namespace) return false
+    if (disambiguatingDescription != other.disambiguatingDescription) return false
+    if (identifier != other.identifier) return false
+    if (name != other.name) return false
+    if (additionalProperties != other.additionalProperties) return false
+    return true
+  }
+
+  public final override fun hashCode(): Int =
+    Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
+
+  public final override fun toString(): String {
+    val attributes = mutableMapOf<String, String>()
+    if (namespace != null) {
+      attributes["namespace"] = namespace
+    }
+    if (disambiguatingDescription != null) {
+      attributes["disambiguatingDescription"] =
+        disambiguatingDescription.toString(includeWrapperName = false)
+    }
+    if (identifier != null) {
+      attributes["identifier"] = identifier
+    }
+    if (name != null) {
+      attributes["name"] = name.toString(includeWrapperName = false)
+    }
+    attributes += additionalProperties.map { (k, v) -> k to v.toString() }
+    val commaSeparated = attributes.entries.joinToString(separator = ", ") { (k, v) -> """$k=$v""" }
+    return """$selfTypeName($commaSeparated)"""
+  }
+
+  /**
+   * An abstract implementation of [CommonExecutionStatus.Builder].
+   *
+   * Allows for extension like:
+   * ```kt
+   * class MyCommonExecutionStatus :
+   *   : AbstractCommonExecutionStatus<
+   *     MyCommonExecutionStatus,
+   *     MyCommonExecutionStatus.Builder>(...) {
+   *
+   *   class Builder
+   *   : Builder<
+   *       Builder,
+   *       MyCommonExecutionStatus
+   *   >() {
+   *     private var foo: String? = null
+   *     private val bars = mutableListOf<Int>()
+   *
+   *     override val selfTypeName =
+   *       "MyCommonExecutionStatus.Builder"
+   *
+   *     override val additionalProperties: Map<String, Any?>
+   *       get() = mapOf("foo" to foo, "bars" to bars)
+   *
+   *     override fun buildFromCommonExecutionStatus(
+   *       commonExecutionStatus: CommonExecutionStatus
+   *     ): MyCommonExecutionStatus {
+   *       return MyCommonExecutionStatus(
+   *         commonExecutionStatus,
+   *         foo,
+   *         bars.toList()
+   *       )
+   *     }
+   *
+   *     fun setFoo(string: String): Builder {
+   *       return apply { foo = string }
+   *     }
+   *
+   *     fun addBar(int: Int): Builder {
+   *       return apply { bars += int }
+   *     }
+   *
+   *     fun addBars(values: Iterable<Int>): Builder {
+   *       return apply { bars += values }
+   *     }
+   *   }
+   * }
+   * ```
+   *
+   * Also see [AbstractCommonExecutionStatus].
+   */
+  @Suppress("StaticFinalBuilder")
+  public abstract class Builder<
+    Self : Builder<Self, Built>, Built : AbstractCommonExecutionStatus<Built, Self>> :
+    CommonExecutionStatus.Builder<Self> {
+    /**
+     * Human readable name for the concrete [Self] class.
+     *
+     * Used in the [toString] output.
+     */
+    @get:Suppress("GetterOnBuilder") protected abstract val selfTypeName: String
+
+    /**
+     * The additional properties that exist on the concrete [Self] class.
+     *
+     * Used for equality comparison and computing the hash code.
+     */
+    @get:Suppress("GetterOnBuilder") protected abstract val additionalProperties: Map<String, Any?>
+
+    private var namespace: String? = null
+
+    private var disambiguatingDescription: DisambiguatingDescription? = null
+
+    private var identifier: String? = null
+
+    private var name: Name? = null
+
+    /**
+     * Builds a concrete [Built] instance, given a built [CommonExecutionStatus].
+     *
+     * Subclasses should override this method to build a concrete [Built] instance that holds both
+     * the [CommonExecutionStatus]-specific properties and the subclass specific
+     * [additionalProperties].
+     *
+     * See the sample code in the documentation of this class for more context.
+     */
+    @Suppress("BuilderSetStyle")
+    protected abstract fun buildFromCommonExecutionStatus(
+      commonExecutionStatus: CommonExecutionStatus
+    ): Built
+
+    public final override fun build(): Built =
+      buildFromCommonExecutionStatus(
+        CommonExecutionStatusImpl(namespace, disambiguatingDescription, identifier, name)
+      )
+
+    public final override fun setNamespace(namespace: String?): Self {
+      this.namespace = namespace
+      return this as Self
+    }
+
+    public final override fun setDisambiguatingDescription(
+      disambiguatingDescription: DisambiguatingDescription?
+    ): Self {
+      this.disambiguatingDescription = disambiguatingDescription
+      return this as Self
+    }
+
+    public final override fun setIdentifier(text: String?): Self {
+      this.identifier = text
+      return this as Self
+    }
+
+    public final override fun setName(name: Name?): Self {
+      this.name = name
+      return this as Self
+    }
+
+    @Suppress("BuilderSetStyle")
+    public final override fun equals(other: Any?): Boolean {
+      if (this === other) return true
+      if (other == null || this::class.java != other::class.java) return false
+      other as Self
+      if (namespace != other.namespace) return false
+      if (disambiguatingDescription != other.disambiguatingDescription) return false
+      if (identifier != other.identifier) return false
+      if (name != other.name) return false
+      if (additionalProperties != other.additionalProperties) return false
+      return true
+    }
+
+    @Suppress("BuilderSetStyle")
+    public final override fun hashCode(): Int =
+      Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
+
+    @Suppress("BuilderSetStyle")
+    public final override fun toString(): String {
+      val attributes = mutableMapOf<String, String>()
+      if (namespace != null) {
+        attributes["namespace"] = namespace!!
+      }
+      if (disambiguatingDescription != null) {
+        attributes["disambiguatingDescription"] =
+          disambiguatingDescription!!.toString(includeWrapperName = false)
+      }
+      if (identifier != null) {
+        attributes["identifier"] = identifier!!
+      }
+      if (name != null) {
+        attributes["name"] = name!!.toString(includeWrapperName = false)
+      }
+      attributes += additionalProperties.map { (k, v) -> k to v.toString() }
+      val commaSeparated =
+        attributes.entries.joinToString(separator = ", ") { (k, v) -> """$k=$v""" }
+      return """$selfTypeName($commaSeparated)"""
+    }
+  }
+}
+
+private class CommonExecutionStatusImpl :
+  AbstractCommonExecutionStatus<CommonExecutionStatusImpl, CommonExecutionStatusImpl.Builder> {
+  protected override val selfTypeName: String
+    get() = "CommonExecutionStatus"
+
+  protected override val additionalProperties: Map<String, Any?>
+    get() = emptyMap()
+
+  public constructor(
+    namespace: String?,
+    disambiguatingDescription: DisambiguatingDescription?,
+    identifier: String?,
+    name: Name?,
+  ) : super(namespace, disambiguatingDescription, identifier, name)
+
+  public constructor(commonExecutionStatus: CommonExecutionStatus) : super(commonExecutionStatus)
+
+  protected override fun toBuilderWithAdditionalPropertiesOnly(): Builder = Builder()
+
+  public class Builder :
+    AbstractCommonExecutionStatus.Builder<Builder, CommonExecutionStatusImpl>() {
+    protected override val selfTypeName: String
+      get() = "CommonExecutionStatus.Builder"
+
+    protected override val additionalProperties: Map<String, Any?>
+      get() = emptyMap()
+
+    protected override fun buildFromCommonExecutionStatus(
+      commonExecutionStatus: CommonExecutionStatus
+    ): CommonExecutionStatusImpl =
+      commonExecutionStatus as? CommonExecutionStatusImpl
+        ?: CommonExecutionStatusImpl(commonExecutionStatus)
+  }
+}
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/DayOfWeek.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/DayOfWeek.kt
index 737ebe2..6f17b6e 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/DayOfWeek.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/DayOfWeek.kt
@@ -19,7 +19,7 @@
 /**
  * The day of the week.
  *
- * See http://schema.org/DayOfWeek for context.
+ * See https://schema.org/DayOfWeek for context.
  *
  * Represents an open enum. See [Companion] for the different possible variants. More variants may
  * be added over time.
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ExecutionStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ExecutionStatus.kt
index 2ead4f6..fc8431a 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ExecutionStatus.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ExecutionStatus.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -30,13 +31,18 @@
 import kotlin.jvm.JvmStatic
 
 /**
- * Status of a task that was pending execution.
+ * A parent type that serves as the umbrella for a number of types that represent the status of a
+ * pending task.
  *
- * See http://schema.googleapis.com/ExecutionStatus for context.
+ * Prefer one of the subtypes in most contexts to represent a specific type of status e.g.
+ * `UnsupportedOperationStatus`.
+ *
+ * See https://schema.googleapis.com/ExecutionStatus for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractExecutionStatus] if you need to extend this type.
  */
+@Document(name = "bit:ExecutionStatus")
 public interface ExecutionStatus : Intangible {
   /** Converts this [ExecutionStatus] to its builder with all the properties copied over. */
   public override fun toBuilder(): Builder<*>
@@ -143,16 +149,16 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
-    Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+    Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
   public final override fun toString(): String {
     val attributes = mutableMapOf<String, String>()
@@ -294,17 +300,17 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
 
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
-      Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+      Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
     @Suppress("BuilderSetStyle")
     public final override fun toString(): String {
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/GenericErrorStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/GenericErrorStatus.kt
index f9b637c..02047e6 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/GenericErrorStatus.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/GenericErrorStatus.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -32,12 +33,13 @@
 /**
  * Status indicating that the task was not executed successfully.
  *
- * See http://schema.googleapis.com/GenericErrorStatus for context.
+ * See https://schema.googleapis.com/GenericErrorStatus for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractGenericErrorStatus] if you need to extend this type.
  */
-public interface GenericErrorStatus : ExecutionStatus {
+@Document(name = "bit:GenericErrorStatus")
+public interface GenericErrorStatus : CommonExecutionStatus {
   /** Converts this [GenericErrorStatus] to its builder with all the properties copied over. */
   public override fun toBuilder(): Builder<*>
 
@@ -52,7 +54,7 @@
    * Should not be directly implemented. More methods may be added over time. See
    * [AbstractGenericErrorStatus.Builder] if you need to extend this builder.
    */
-  public interface Builder<Self : Builder<Self>> : ExecutionStatus.Builder<Self> {
+  public interface Builder<Self : Builder<Self>> : CommonExecutionStatus.Builder<Self> {
     /** Returns a built [GenericErrorStatus]. */
     public override fun build(): GenericErrorStatus
   }
@@ -144,16 +146,16 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
-    Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+    Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
   public final override fun toString(): String {
     val attributes = mutableMapOf<String, String>()
@@ -298,17 +300,17 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
 
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
-      Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+      Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
     @Suppress("BuilderSetStyle")
     public final override fun toString(): String {
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Intangible.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Intangible.kt
index d5100fa..280714d 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Intangible.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Intangible.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -33,11 +34,12 @@
  * A utility class that serves as the umbrella for a number of 'intangible' things such as
  * quantities, structured values, etc.
  *
- * See http://schema.org/Intangible for context.
+ * See https://schema.org/Intangible for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractIntangible] if you need to extend this type.
  */
+@Document(name = "bit:Intangible")
 public interface Intangible : Thing {
   /** Converts this [Intangible] to its builder with all the properties copied over. */
   public override fun toBuilder(): Builder<*>
@@ -141,16 +143,16 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
-    Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+    Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
   public final override fun toString(): String {
     val attributes = mutableMapOf<String, String>()
@@ -290,17 +292,17 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
 
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
-      Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+      Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
     @Suppress("BuilderSetStyle")
     public final override fun toString(): String {
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ObjectCreationLimitReachedStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ObjectCreationLimitReachedStatus.kt
index 804c394..c677643 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ObjectCreationLimitReachedStatus.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ObjectCreationLimitReachedStatus.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -33,12 +34,13 @@
  * Status indicating that the number of objects have reached the limit and more objects cannot be
  * created.
  *
- * See http://schema.googleapis.com/ObjectCreationLimitReachedStatus for context.
+ * See https://schema.googleapis.com/ObjectCreationLimitReachedStatus for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractObjectCreationLimitReachedStatus] if you need to extend
  * this type.
  */
+@Document(name = "bit:ObjectCreationLimitReachedStatus")
 public interface ObjectCreationLimitReachedStatus : ExecutionStatus {
   /**
    * Converts this [ObjectCreationLimitReachedStatus] to its builder with all the properties copied
@@ -152,16 +154,16 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
-    Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+    Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
   public final override fun toString(): String {
     val attributes = mutableMapOf<String, String>()
@@ -306,17 +308,17 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
 
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
-      Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+      Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
     @Suppress("BuilderSetStyle")
     public final override fun toString(): String {
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Person.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Person.kt
index 471b97b..39c3f36 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Person.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Person.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -32,18 +33,26 @@
 /**
  * A person (alive, dead, undead, or fictional).
  *
- * See http://schema.org/Person for context.
+ * See https://schema.org/Person for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractPerson] if you need to extend this type.
  */
+@Document(name = "bit:Person")
 public interface Person : Thing {
   /**
    * Email address.
    *
-   * See http://schema.org/email for more context.
+   * See https://schema.org/email for more context.
    */
-  public val email: String?
+  @get:Document.StringProperty public val email: String?
+
+  /**
+   * The telephone number.
+   *
+   * See http://schema.org/telephone for more context.
+   */
+  @get:Document.StringProperty public val telephone: String?
 
   /** Converts this [Person] to its builder with all the properties copied over. */
   public override fun toBuilder(): Builder<*>
@@ -65,6 +74,9 @@
 
     /** Sets the `email`. */
     public fun setEmail(text: String?): Self
+
+    /** Sets the `telephone`. */
+    public fun setTelephone(text: String?): Self
   }
 }
 
@@ -109,6 +121,7 @@
 internal constructor(
   public final override val namespace: String?,
   public final override val email: String?,
+  public final override val telephone: String?,
   public final override val disambiguatingDescription: DisambiguatingDescription?,
   public final override val identifier: String?,
   public final override val name: Name?,
@@ -133,6 +146,7 @@
   ) : this(
     person.namespace,
     person.email,
+    person.telephone,
     person.disambiguatingDescription,
     person.identifier,
     person.name
@@ -145,6 +159,7 @@
     toBuilderWithAdditionalPropertiesOnly()
       .setNamespace(namespace)
       .setEmail(email)
+      .setTelephone(telephone)
       .setDisambiguatingDescription(disambiguatingDescription)
       .setIdentifier(identifier)
       .setName(name)
@@ -153,22 +168,24 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (email != other.email) return false
+    if (telephone != other.telephone) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
     Objects.hash(
+      namespace,
       email,
+      telephone,
       disambiguatingDescription,
       identifier,
       name,
-      namespace,
       additionalProperties
     )
 
@@ -180,6 +197,9 @@
     if (email != null) {
       attributes["email"] = email
     }
+    if (telephone != null) {
+      attributes["telephone"] = telephone
+    }
     if (disambiguatingDescription != null) {
       attributes["disambiguatingDescription"] =
         disambiguatingDescription.toString(includeWrapperName = false)
@@ -267,6 +287,8 @@
 
     private var email: String? = null
 
+    private var telephone: String? = null
+
     private var disambiguatingDescription: DisambiguatingDescription? = null
 
     private var identifier: String? = null
@@ -284,7 +306,9 @@
     @Suppress("BuilderSetStyle") protected abstract fun buildFromPerson(person: Person): Built
 
     public final override fun build(): Built =
-      buildFromPerson(PersonImpl(namespace, email, disambiguatingDescription, identifier, name))
+      buildFromPerson(
+        PersonImpl(namespace, email, telephone, disambiguatingDescription, identifier, name)
+      )
 
     public final override fun setNamespace(namespace: String?): Self {
       this.namespace = namespace
@@ -296,6 +320,11 @@
       return this as Self
     }
 
+    public final override fun setTelephone(text: String?): Self {
+      this.telephone = text
+      return this as Self
+    }
+
     public final override fun setDisambiguatingDescription(
       disambiguatingDescription: DisambiguatingDescription?
     ): Self {
@@ -318,11 +347,12 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (email != other.email) return false
+      if (telephone != other.telephone) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
@@ -330,11 +360,12 @@
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
       Objects.hash(
+        namespace,
         email,
+        telephone,
         disambiguatingDescription,
         identifier,
         name,
-        namespace,
         additionalProperties
       )
 
@@ -347,6 +378,9 @@
       if (email != null) {
         attributes["email"] = email!!
       }
+      if (telephone != null) {
+        attributes["telephone"] = telephone!!
+      }
       if (disambiguatingDescription != null) {
         attributes["disambiguatingDescription"] =
           disambiguatingDescription!!.toString(includeWrapperName = false)
@@ -375,10 +409,11 @@
   public constructor(
     namespace: String?,
     email: String?,
+    telephone: String?,
     disambiguatingDescription: DisambiguatingDescription?,
     identifier: String?,
     name: Name?,
-  ) : super(namespace, email, disambiguatingDescription, identifier, name)
+  ) : super(namespace, email, telephone, disambiguatingDescription, identifier, name)
 
   public constructor(person: Person) : super(person)
 
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Schedule.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Schedule.kt
index 07e103c..5daa277 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Schedule.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Schedule.kt
@@ -22,6 +22,7 @@
 import androidx.appactions.builtintypes.properties.RepeatFrequency
 import androidx.appactions.builtintypes.properties.StartDate
 import androidx.appactions.builtintypes.properties.StartTime
+import androidx.appsearch.`annotation`.Document
 import java.time.Duration
 import java.time.Instant
 import java.time.LocalDate
@@ -56,50 +57,51 @@
  * will take place, in addition to its start and end time. Schedules may also have start and end
  * dates to indicate when they are active, e.g. to define a limited calendar of events.
  *
- * See http://schema.org/Schedule for context.
+ * See https://schema.org/Schedule for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractSchedule] if you need to extend this type.
  */
+@Document(name = "bit:Schedule")
 public interface Schedule : Intangible {
   /**
    * Defines the day(s) of the week on which a recurring Event takes place.
    *
-   * See http://schema.org/byDay for more context.
+   * See https://schema.org/byDay for more context.
    */
-  public val byDays: List<ByDay>
+  @get:Document.DocumentProperty(name = "byDay") public val byDays: List<ByDay>
 
   /**
    * Defines the month(s) of the year on which a recurring Event takes place. Specified as an
    * Integer between 1-12. January is 1.
    *
-   * See http://schema.org/byMonth for more context.
+   * See https://schema.org/byMonth for more context.
    */
-  public val byMonths: List<Long>
+  @get:Document.LongProperty(name = "byMonth") public val byMonths: List<Long>
 
   /**
    * Defines the day(s) of the month on which a recurring Event takes place. Specified as an Integer
    * between 1-31.
    *
-   * See http://schema.org/byMonthDay for more context.
+   * See https://schema.org/byMonthDay for more context.
    */
-  public val byMonthDays: List<Long>
+  @get:Document.LongProperty(name = "byMonthDay") public val byMonthDays: List<Long>
 
   /**
    * Defines the week(s) of the month on which a recurring Event takes place. Specified as an
    * Integer between 1-5. For clarity, byMonthWeek is best used in conjunction with byDay to
    * indicate concepts like the first and third Mondays of a month.
    *
-   * See http://schema.org/byMonthWeek for more context.
+   * See https://schema.org/byMonthWeek for more context.
    */
-  public val byMonthWeeks: List<Long>
+  @get:Document.LongProperty(name = "byMonthWeek") public val byMonthWeeks: List<Long>
 
   /**
    * The end date and time of the item.
    *
-   * See http://schema.org/endDate for more context.
+   * See https://schema.org/endDate for more context.
    */
-  public val endDate: EndDate?
+  @get:Document.DocumentProperty public val endDate: EndDate?
 
   /**
    * The endTime of something.
@@ -109,9 +111,9 @@
    * John wrote a book from January to *December*. For media, including audio and video, it's the
    * time offset of the end of a clip within a larger file.
    *
-   * See http://schema.org/endTime for more context.
+   * See https://schema.org/endTime for more context.
    */
-  public val endTime: EndTime?
+  @get:Document.DocumentProperty public val endTime: EndTime?
 
   /**
    * Defines a `Date` or `DateTime` during which a scheduled `Event` will not take place. The
@@ -121,39 +123,39 @@
    * scheduled for that 24 hour period should be excluded from the schedule. This allows a whole day
    * to be excluded from the schedule without having to itemise every scheduled event.
    *
-   * See http://schema.org/exceptDate for more context.
+   * See https://schema.org/exceptDate for more context.
    */
-  public val exceptDate: ExceptDate?
+  @get:Document.DocumentProperty public val exceptDate: ExceptDate?
 
   /**
    * Defines the number of times a recurring `Event` will take place.
    *
-   * See http://schema.org/repeatCount for more context.
+   * See https://schema.org/repeatCount for more context.
    */
-  @get:Suppress("AutoBoxing") public val repeatCount: Long?
+  @get:Document.LongProperty @get:Suppress("AutoBoxing") public val repeatCount: Long?
 
   /**
    * Defines the frequency at which `Event`s will occur according to a schedule `Schedule`. The
    * intervals between events should be defined as a `Duration` of time.
    *
-   * See http://schema.org/repeatFrequency for more context.
+   * See https://schema.org/repeatFrequency for more context.
    */
-  public val repeatFrequency: RepeatFrequency?
+  @get:Document.DocumentProperty public val repeatFrequency: RepeatFrequency?
 
   /**
    * Indicates the timezone for which the time(s) indicated in the `Schedule` are given. The value
    * provided should be among those listed in the IANA Time Zone Database.
    *
-   * See http://schema.org/scheduleTimezone for more context.
+   * See https://schema.org/scheduleTimezone for more context.
    */
-  public val scheduleTimezone: String?
+  @get:Document.StringProperty public val scheduleTimezone: String?
 
   /**
    * The start date and time of the item.
    *
-   * See http://schema.org/startDate for more context.
+   * See https://schema.org/startDate for more context.
    */
-  public val startDate: StartDate?
+  @get:Document.DocumentProperty public val startDate: StartDate?
 
   /**
    * The startTime of something.
@@ -163,9 +165,9 @@
    * John wrote a book from *January* to December. For media, including audio and video, it's the
    * time offset of the start of a clip within a larger file.
    *
-   * See http://schema.org/startTime for more context.
+   * See https://schema.org/startTime for more context.
    */
-  public val startTime: StartTime?
+  @get:Document.DocumentProperty public val startTime: StartTime?
 
   /** Converts this [Schedule] to its builder with all the properties copied over. */
   public override fun toBuilder(): Builder<*>
@@ -426,6 +428,7 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (byDays != other.byDays) return false
     if (byMonths != other.byMonths) return false
     if (byMonthDays != other.byMonthDays) return false
@@ -441,13 +444,13 @@
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
     Objects.hash(
+      namespace,
       byDays,
       byMonths,
       byMonthDays,
@@ -463,7 +466,6 @@
       disambiguatingDescription,
       identifier,
       name,
-      namespace,
       additionalProperties
     )
 
@@ -782,6 +784,7 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (byDays != other.byDays) return false
       if (byMonths != other.byMonths) return false
       if (byMonthDays != other.byMonthDays) return false
@@ -797,7 +800,6 @@
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
@@ -805,6 +807,7 @@
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
       Objects.hash(
+        namespace,
         byDays,
         byMonths,
         byMonthDays,
@@ -820,7 +823,6 @@
         disambiguatingDescription,
         identifier,
         name,
-        namespace,
         additionalProperties
       )
 
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/SuccessStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/SuccessStatus.kt
index 8655af4..8518775 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/SuccessStatus.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/SuccessStatus.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -32,12 +33,13 @@
 /**
  * Status indicating that the task was executed successfully.
  *
- * See http://schema.googleapis.com/SuccessStatus for context.
+ * See https://schema.googleapis.com/SuccessStatus for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractSuccessStatus] if you need to extend this type.
  */
-public interface SuccessStatus : ExecutionStatus {
+@Document(name = "bit:SuccessStatus")
+public interface SuccessStatus : CommonExecutionStatus {
   /** Converts this [SuccessStatus] to its builder with all the properties copied over. */
   public override fun toBuilder(): Builder<*>
 
@@ -52,7 +54,7 @@
    * Should not be directly implemented. More methods may be added over time. See
    * [AbstractSuccessStatus.Builder] if you need to extend this builder.
    */
-  public interface Builder<Self : Builder<Self>> : ExecutionStatus.Builder<Self> {
+  public interface Builder<Self : Builder<Self>> : CommonExecutionStatus.Builder<Self> {
     /** Returns a built [SuccessStatus]. */
     public override fun build(): SuccessStatus
   }
@@ -143,16 +145,16 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
-    Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+    Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
   public final override fun toString(): String {
     val attributes = mutableMapOf<String, String>()
@@ -294,17 +296,17 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
 
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
-      Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+      Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
     @Suppress("BuilderSetStyle")
     public final override fun toString(): String {
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Thing.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Thing.kt
index feb6dda..d359e77 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Thing.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Thing.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -32,38 +33,39 @@
 /**
  * The most generic type of item.
  *
- * See http://schema.org/Thing for context.
+ * See https://schema.org/Thing for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractThing] if you need to extend this type.
  */
+@Document(name = "bit:Thing")
 public interface Thing {
   /** Represents the AppSearch document's namespace. */
-  public val namespace: String?
+  @get:Document.Namespace public val namespace: String?
 
   /**
    * A sub property of description. A short description of the item used to disambiguate from other,
    * similar items. Information from other properties (in particular, name) may be necessary for the
    * description to be useful for disambiguation.
    *
-   * See http://schema.org/disambiguatingDescription for more context.
+   * See https://schema.org/disambiguatingDescription for more context.
    */
-  public val disambiguatingDescription: DisambiguatingDescription?
+  @get:Document.DocumentProperty public val disambiguatingDescription: DisambiguatingDescription?
 
   /**
    * The identifier property represents any kind of identifier for any kind of Thing, such as ISBNs,
    * GTIN codes, UUIDs etc.
    *
-   * See http://schema.org/identifier for more context.
+   * See https://schema.org/identifier for more context.
    */
-  public val identifier: String?
+  @get:Document.Id public val identifier: String?
 
   /**
    * The name of the item.
    *
-   * See http://schema.org/name for more context.
+   * See https://schema.org/name for more context.
    */
-  public val name: Name?
+  @get:Document.DocumentProperty public val name: Name?
 
   /** Converts this [Thing] to its builder with all the properties copied over. */
   public fun toBuilder(): Builder<*>
@@ -84,6 +86,7 @@
     /** Returns a built [Thing]. */
     public fun build(): Thing
 
+    /** Sets the `namespace`. */
     public fun setNamespace(namespace: String?): Self
 
     /** Sets the `disambiguatingDescription` to [String]. */
@@ -183,16 +186,16 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
-    Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+    Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
   public final override fun toString(): String {
     val attributes = mutableMapOf<String, String>()
@@ -330,17 +333,17 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
 
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
-      Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+      Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
     @Suppress("BuilderSetStyle")
     public final override fun toString(): String {
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Timer.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Timer.kt
index bbbc685..ecc155b 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Timer.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Timer.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.time.Duration
 import java.util.Objects
 import kotlin.Any
@@ -33,16 +34,17 @@
 /**
  * A timer to go off at a particular time.
  *
- * See http://schema.googleapis.com/Timer for context.
+ * See https://schema.googleapis.com/Timer for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractTimer] if you need to extend this type.
  */
+@Document(name = "bit:Timer")
 public interface Timer : Thing {
   /**
    * The duration of the item (movie, audio recording, event, etc.).
    *
-   * See http://schema.org/duration for more context.
+   * See https://schema.org/duration for more context.
    */
   public val duration: Duration?
 
@@ -154,22 +156,22 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (duration != other.duration) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
     Objects.hash(
+      namespace,
       duration,
       disambiguatingDescription,
       identifier,
       name,
-      namespace,
       additionalProperties
     )
 
@@ -319,11 +321,11 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (duration != other.duration) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
@@ -331,11 +333,11 @@
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
       Objects.hash(
+        namespace,
         duration,
         disambiguatingDescription,
         identifier,
         name,
-        namespace,
         additionalProperties
       )
 
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/UnsupportedOperationStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/UnsupportedOperationStatus.kt
index 547ea02..62d57ca 100644
--- a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/UnsupportedOperationStatus.kt
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/UnsupportedOperationStatus.kt
@@ -15,6 +15,7 @@
 
 import androidx.appactions.builtintypes.properties.DisambiguatingDescription
 import androidx.appactions.builtintypes.properties.Name
+import androidx.appsearch.`annotation`.Document
 import java.util.Objects
 import kotlin.Any
 import kotlin.Boolean
@@ -32,12 +33,13 @@
 /**
  * Status indicating that the operation is not supported, e.g. updating an unsupported field.
  *
- * See http://schema.googleapis.com/UnsupportedOperationStatus for context.
+ * See https://schema.googleapis.com/UnsupportedOperationStatus for context.
  *
  * Should not be directly implemented. More properties may be added over time. Instead consider
  * using [Companion.Builder] or see [AbstractUnsupportedOperationStatus] if you need to extend this
  * type.
  */
+@Document(name = "bit:UnsupportedOperationStatus")
 public interface UnsupportedOperationStatus : ExecutionStatus {
   /**
    * Converts this [UnsupportedOperationStatus] to its builder with all the properties copied over.
@@ -150,16 +152,16 @@
     if (this === other) return true
     if (other == null || this::class.java != other::class.java) return false
     other as Self
+    if (namespace != other.namespace) return false
     if (disambiguatingDescription != other.disambiguatingDescription) return false
     if (identifier != other.identifier) return false
     if (name != other.name) return false
-    if (namespace != other.namespace) return false
     if (additionalProperties != other.additionalProperties) return false
     return true
   }
 
   public final override fun hashCode(): Int =
-    Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+    Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
   public final override fun toString(): String {
     val attributes = mutableMapOf<String, String>()
@@ -304,17 +306,17 @@
       if (this === other) return true
       if (other == null || this::class.java != other::class.java) return false
       other as Self
+      if (namespace != other.namespace) return false
       if (disambiguatingDescription != other.disambiguatingDescription) return false
       if (identifier != other.identifier) return false
       if (name != other.name) return false
-      if (namespace != other.namespace) return false
       if (additionalProperties != other.additionalProperties) return false
       return true
     }
 
     @Suppress("BuilderSetStyle")
     public final override fun hashCode(): Int =
-      Objects.hash(disambiguatingDescription, identifier, name, namespace, additionalProperties)
+      Objects.hash(namespace, disambiguatingDescription, identifier, name, additionalProperties)
 
     @Suppress("BuilderSetStyle")
     public final override fun toString(): String {
diff --git a/appactions/interaction/integration-tests/testapp/src/main/AndroidManifest.xml b/appactions/interaction/integration-tests/testapp/src/main/AndroidManifest.xml
index dcf7181..4a7733f 100644
--- a/appactions/interaction/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/appactions/interaction/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -18,16 +18,17 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools">
 
-  <application
-      android:allowBackup="true"
-      android:dataExtractionRules="@xml/data_extraction_rules"
-      android:fullBackupContent="@xml/backup_rules"
-      android:icon="@mipmap/ic_launcher"
-      android:label="@string/app_name"
-      android:roundIcon="@mipmap/ic_launcher_round"
-      android:supportsRtl="true"
-      android:theme="@style/Theme.AppInteractionTest"
-      tools:targetApi="31">
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.AppInteractionTest"
+        tools:targetApi="31">
+        <activity android:name=".Alarm.AlarmActivity" />
     <activity
         android:name=".MainActivity"
         android:exported="true">
@@ -50,4 +51,5 @@
       </intent-filter>
     </service>
   </application>
+    <uses-permission android:name="android.permission.VIBRATE" />
 </manifest>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/ClockInteractionService.kt b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/ClockInteractionService.kt
index 6c4037e..2c4f855 100644
--- a/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/ClockInteractionService.kt
+++ b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/ClockInteractionService.kt
@@ -16,6 +16,8 @@
 
 package androidx.appactions.interaction.testapp
 
+import android.content.Context
+import android.content.SharedPreferences
 import android.os.Handler
 import android.os.Looper
 import android.widget.Toast
@@ -23,16 +25,29 @@
 import androidx.appactions.interaction.capabilities.core.ExecutionCallback
 import androidx.appactions.interaction.capabilities.core.ExecutionResult
 import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.productivity.PauseTimer
+import androidx.appactions.interaction.capabilities.productivity.ResumeTimer
 import androidx.appactions.interaction.capabilities.productivity.StartTimer
+import androidx.appactions.interaction.capabilities.productivity.StopTimer
 import androidx.appactions.interaction.service.AppInteractionService
 import androidx.appactions.interaction.service.AppVerificationInfo
 import java.time.Duration
 
+const val KEY_TIMER_SHARED_PREFERENCES = "timer_shared_preferences"
+const val KEY_TIMER_STARTED_AT = "timer_started_at"
+const val KEY_TIMER_NAME = "timer_name"
+const val KEY_TIMER_DURATION = "timer_duration"
+const val KEY_TIMER_PAUSED_AT = "timer_paused_at"
+
 class ClockInteractionService : AppInteractionService() {
 
-    lateinit var mHandler: Handler
+    private lateinit var mHandler: Handler
+    private var duration: Long = 0L
+    private lateinit var sharedPreferences: SharedPreferences
     override fun onCreate() {
         super.onCreate()
+        sharedPreferences = applicationContext
+            .getSharedPreferences(KEY_TIMER_SHARED_PREFERENCES, Context.MODE_PRIVATE)
         mHandler = Handler(Looper.myLooper()!!)
     }
 
@@ -46,22 +61,66 @@
         })
     }
 
-    private val capability =
+    private val startTimerCapability =
         StartTimer.CapabilityBuilder()
             .setId("start_timer_oneshot")
             .setDurationProperty(Property<Duration>(isRequiredForExecution = true))
             .setExecutionCallback(ExecutionCallback<StartTimer.Arguments, StartTimer.Output> {
+                val result = ExecutionResult.Builder<StartTimer.Output>().build()
                 val name = it.name ?: "Default title"
                 val duration = it.duration!!
+                if (sharedPreferences.getBoolean(KEY_IS_TIMER_RUNNING, false)) {
+                    showToast(msg = "Could not start a timer because one is already running")
+                    return@ExecutionCallback result
+                }
+                sharedPreferences.edit().putLong(KEY_TIMER_STARTED_AT, System.currentTimeMillis())
+                sharedPreferences.edit().putBoolean(KEY_IS_TIMER_RUNNING, true)
+                sharedPreferences.edit().putString(KEY_TIMER_NAME, name)
+                sharedPreferences.edit().putLong(KEY_TIMER_DURATION, duration.toMillis())
+                showToast(msg = "Timer Name:$name Duration:$duration has been started")
 
-                // Do execution. ie. create the Timer called $name for $duration
-                showToast(msg = "Name:$name Duration:$duration")
-
-                ExecutionResult.Builder<StartTimer.Output>().build()
+                result
             })
             .build()
 
-    override val registeredCapabilities: List<Capability> = listOf(capability)
+    private val pauseTimerCapability =
+        PauseTimer.CapabilityBuilder()
+            .setId("pause_timer_oneshot")
+            .setExecutionCallback(ExecutionCallback {
+                sharedPreferences.edit().putLong(KEY_TIMER_PAUSED_AT, System.currentTimeMillis())
+                ExecutionResult.Builder<PauseTimer.Output>().build()
+            })
+            .build()
+
+    private val resumeTimerCapability =
+        ResumeTimer.CapabilityBuilder()
+            .setId("resume_timer_oneshot")
+            .setExecutionCallback(ExecutionCallback {
+                val pausedTime = sharedPreferences.getLong(KEY_TIMER_PAUSED_AT, 0L)
+                val startedTime = sharedPreferences.getLong(KEY_TIMER_STARTED_AT, 0L)
+                val elapsedTime = pausedTime - startedTime
+                val duration = duration - elapsedTime
+                sharedPreferences.edit().putLong(KEY_TIMER_STARTED_AT, System.currentTimeMillis())
+                sharedPreferences.edit().putLong(KEY_TIMER_DURATION, duration)
+                ExecutionResult.Builder<ResumeTimer.Output>().build()
+            })
+            .build()
+
+    private val stopTimerCapability =
+        StopTimer.CapabilityBuilder()
+            .setId("resume_timer_oneshot")
+            .setExecutionCallback(ExecutionCallback {
+                sharedPreferences.edit().putBoolean(KEY_IS_TIMER_RUNNING, false)
+                ExecutionResult.Builder<StopTimer.Output>().build()
+            })
+            .build()
+
+    override val registeredCapabilities: List<Capability> = listOf(
+        startTimerCapability,
+        pauseTimerCapability,
+        resumeTimerCapability,
+        stopTimerCapability,
+    )
     override val allowedApps: List<AppVerificationInfo> = listOf(
         AppVerificationInfo.Builder()
             .addSignature(hex2Byte(ASSISTANT_SIGNATURE))
diff --git a/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/MainActivity.kt b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/MainActivity.kt
index c9fbc82..4319682 100644
--- a/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/MainActivity.kt
+++ b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/MainActivity.kt
@@ -1,54 +1,112 @@
 package androidx.appactions.interaction.testapp
 
+import android.content.Context
+import android.content.SharedPreferences
 import android.os.Bundle
 import android.os.CountDownTimer
 import android.widget.TextView
-import android.widget.Toast
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.widget.AppCompatButton
 
+const val KEY_IS_TIMER_PAUSED = "timer_paused"
+const val KEY_IS_TIMER_RUNNING = "timer_running"
+const val DEFAULT_DURATION = 120 * 1000L
+
 class MainActivity : AppCompatActivity() {
-  private var duration = 120
-  private var hasRunningTimer = false
+    private var remainingDuration = 0L
+    private var hasRunningTimer = false
+    private var startButton: AppCompatButton? = null
+    private lateinit var timer: CountDownTimer
+    private lateinit var sharedPreferences: SharedPreferences
+    private lateinit var remainingTimeLabel: TextView
 
-  override fun onCreate(savedInstanceState: Bundle?) {
-    super.onCreate(savedInstanceState)
-    setContentView(R.layout.activity_main)
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+        sharedPreferences = applicationContext
+            .getSharedPreferences(KEY_TIMER_SHARED_PREFERENCES, Context.MODE_PRIVATE)
+        startButton = findViewById(R.id.startButton)
+        remainingTimeLabel = findViewById(R.id.remainingTimeLabel)
+        hasRunningTimer = sharedPreferences.getBoolean(KEY_IS_TIMER_RUNNING, false)
 
-    val hours: TextView = findViewById(R.id.hour)
-    val mins: TextView = findViewById(R.id.minute)
-    val seconds: TextView = findViewById(R.id.second)
-    val startButton: AppCompatButton = findViewById(R.id.startButton)
-
-    startButton.setOnClickListener {
-      if (!hasRunningTimer) {
-        hasRunningTimer = true
-        object : CountDownTimer((duration * 1000).toLong(), 1000) {
-          override fun onTick(millisUntilFinished: Long) {
-            runOnUiThread {
-              val timeArray = millisecondsToTimeString(millisUntilFinished).split(":")
-              hours.text = timeArray[0]
-              mins.text = timeArray[1]
-              seconds.text = timeArray[2]
+        if (hasRunningTimer) {
+            remainingDuration = sharedPreferences.getLong(KEY_TIMER_DURATION, 0L)
+            if (!sharedPreferences.getBoolean(KEY_IS_TIMER_PAUSED, false)) {
+                val startedTime = sharedPreferences.getLong(KEY_TIMER_STARTED_AT, 0L)
+                val elapsedTime = System.currentTimeMillis() - startedTime
+                remainingDuration = remainingDuration - elapsedTime
             }
-          }
+            startTimer(remainingDuration)
+        }
 
-          override fun onFinish() {
-            // Reset timer duration
-            duration = 120
-            hasRunningTimer = false
-          }
-        }.start()
-      } else {
-        Toast.makeText(this@MainActivity, "Timer is already running", Toast.LENGTH_SHORT).show()
-      }
+        startButton?.apply {
+            if (!hasRunningTimer) {
+                text = "Start"
+            } else if (sharedPreferences.getBoolean(KEY_IS_TIMER_PAUSED, false)) {
+                text = "Resume"
+            } else {
+                text = "Pause"
+            }
+            setOnClickListener {
+                setTimer(DEFAULT_DURATION)
+            }
+        }
     }
-  }
+    private fun setTimer(duration: Long) {
+        hasRunningTimer = sharedPreferences.getBoolean(KEY_IS_TIMER_RUNNING, false)
+        if (!hasRunningTimer) {
+            startButton?.text = "Pause"
+            startTimer(duration)
+        } else if (sharedPreferences.getBoolean(KEY_IS_TIMER_PAUSED, false)) {
+            startButton?.text = "Pause"
+            resumeTimer()
+        } else {
+            startButton?.text = "Resume"
+            pauseTimer()
+        }
+    }
+    private fun startTimer(duration: Long) {
+        sharedPreferences.edit().putBoolean(KEY_IS_TIMER_RUNNING, true).apply()
+        timer = object : CountDownTimer(duration, 1000) {
 
-  private fun millisecondsToTimeString(milliseconds: Long): String {
-    val hours = milliseconds / 3600000
-    val minutes = (milliseconds % 3600000) / 60000
-    val seconds = (milliseconds % 60000) / 1000
-    return String.format("%02d:%02d:%02d", hours, minutes, seconds)
-  }
+            override fun onTick(millisUntilFinished: Long) {
+                runOnUiThread {
+                    this@MainActivity.remainingDuration = millisUntilFinished
+                    val timeArray = millisecondsToTimeString(millisUntilFinished).split(":")
+                    remainingTimeLabel.text = "$timeArray[0]:$timeArray[1]:$timeArray[2]"
+                }
+            }
+            override fun onFinish() {
+                // Reset timer duration
+                this@MainActivity.remainingDuration = 0
+                sharedPreferences.edit().putBoolean(KEY_IS_TIMER_RUNNING, false).apply()
+            }
+        }
+        timer.start()
+    }
+
+    private fun pauseTimer() {
+        timer.cancel()
+        sharedPreferences.edit().putBoolean(KEY_IS_TIMER_PAUSED, true).apply()
+        sharedPreferences.edit().putLong(KEY_TIMER_DURATION, this.remainingDuration).apply()
+    }
+
+    private fun resumeTimer() {
+        val duration = sharedPreferences.getLong(KEY_TIMER_DURATION, DEFAULT_DURATION)
+        sharedPreferences.edit().putBoolean(KEY_IS_TIMER_PAUSED, false).apply()
+        sharedPreferences.edit().putLong(KEY_TIMER_STARTED_AT, System.currentTimeMillis()).apply()
+        startTimer(duration)
+    }
+
+    private fun stopTimer() {
+        sharedPreferences.edit().putBoolean(KEY_IS_TIMER_RUNNING, false).apply()
+        sharedPreferences.edit().putBoolean(KEY_IS_TIMER_PAUSED, false).apply()
+    }
+
+    private fun millisecondsToTimeString(milliseconds: Long): String {
+        val hours = milliseconds / 3600000
+        val minutes = (milliseconds % 3600000) / 60000
+        val seconds = (milliseconds % 60000) / 1000
+        return String.format("%02d:%02d:%02d", hours, minutes, seconds)
+    }
 }
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/alarm/AlarmActivity.kt b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/alarm/AlarmActivity.kt
new file mode 100644
index 0000000..25ee1f2
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/alarm/AlarmActivity.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.testapp.Alarm
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.util.Log
+import androidx.appactions.interaction.testapp.R
+import androidx.appcompat.app.AppCompatActivity
+
+const val TAG = "AlarmActivity"
+const val DEFAULT_ALARM_AFTER_CURRENT_MILLIS = 20 * 1000L
+const val VIBRATE_MILLIS = 2000L
+
+class AlarmActivity : AppCompatActivity() {
+    var alarmAfterCurrentMillis: Long = DEFAULT_ALARM_AFTER_CURRENT_MILLIS
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_alarm)
+    }
+
+    class AlarmReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            Log.e(TAG, "System.currentTimeMillis().toString() in AlarmReceiver onReceive()")
+            vibrateDevice(context)
+        }
+
+        // Vibrates the device for 100 milliseconds.
+        fun vibrateDevice(context: Context) {
+            val vibrator = context.getSystemService(Vibrator::class.java)
+            vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE))
+        }
+
+        companion object {
+            private const val TAG = "alarmclock.AlarmReceiver"
+        }
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/alarm/AlarmInteractionService.kt b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/alarm/AlarmInteractionService.kt
new file mode 100644
index 0000000..b607d37
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/alarm/AlarmInteractionService.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.testapp.alarm
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Handler
+import android.os.Looper
+import android.widget.Toast
+import androidx.appactions.builtintypes.types.Schedule
+import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.ExecutionCallback
+import androidx.appactions.interaction.capabilities.core.ExecutionResult
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.core.properties.StringValue
+import androidx.appactions.interaction.capabilities.productivity.CreateAlarm
+import androidx.appactions.interaction.capabilities.productivity.DismissAlarm
+import androidx.appactions.interaction.capabilities.productivity.SnoozeAlarm
+import androidx.appactions.interaction.capabilities.productivity.UpdateAlarm
+import androidx.appactions.interaction.service.AppInteractionService
+import androidx.appactions.interaction.service.AppVerificationInfo
+import androidx.appactions.interaction.testapp.Alarm.AlarmActivity
+import java.util.Calendar
+
+const val KEY_ALARM_SHARED_PREFERENCES = "alarm_shared_preferences"
+const val KEY_ALARM_SCHEDULE = "alarm_schedule"
+const val KEY_ALARM_IS_SET = "alarm_is_set"
+const val KEY_ALARM_IS_SNOOZED = "alarm_is_snoozed"
+
+class AlarmInteractionService : AppInteractionService() {
+
+    private lateinit var mHandler: Handler
+    private lateinit var sharedPreferences: SharedPreferences
+    private lateinit var context: Context
+    private lateinit var intent: Intent
+    private lateinit var alarmIntent: PendingIntent
+    private var schedule: Schedule? = null
+    val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
+
+    override fun onCreate() {
+        super.onCreate()
+        sharedPreferences = applicationContext
+            .getSharedPreferences(KEY_ALARM_SHARED_PREFERENCES, Context.MODE_PRIVATE)
+        mHandler = Handler(Looper.myLooper()!!)
+        context = applicationContext
+        intent = Intent(context, AlarmActivity.AlarmReceiver::class.java)
+        alarmIntent = PendingIntent.getBroadcast(
+            context,
+            0,
+            intent,
+            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+        )
+    }
+    private fun showToast(msg: String) {
+        mHandler.post(Runnable {
+            Toast.makeText(
+                this@AlarmInteractionService,
+                msg,
+                Toast.LENGTH_LONG
+            ).show()
+        })
+    }
+
+    private val createAlarmCapability =
+        CreateAlarm.CapabilityBuilder()
+            .setIdentifierProperty(Property(listOf(StringValue("create_alarm_oneshot"))))
+            .setExecutionCallback(
+                ExecutionCallback {
+                    schedule = it.schedule
+                    sharedPreferences.edit().putString(KEY_ALARM_SCHEDULE, schedule.toString())
+                        .apply()
+                    sharedPreferences.edit().putBoolean(KEY_ALARM_IS_SET, true).apply()
+                    setAlarm()
+                    ExecutionResult.Builder<CreateAlarm.Output>().build()
+                }).build()
+
+    fun setAlarm() {
+        val calendar: Calendar = Calendar.getInstance().apply {
+            timeInMillis = System.currentTimeMillis()
+            set(Calendar.HOUR_OF_DAY, schedule?.startTime?.asTime?.hour ?: 12)
+            set(Calendar.MINUTE, schedule?.startTime?.asTime?.minute ?: 30)
+        }
+        alarmManager.setRepeating(
+            AlarmManager.RTC_WAKEUP,
+            calendar.timeInMillis,
+            schedule?.repeatFrequency?.asDuration?.toMillis() ?: -1,
+            alarmIntent,
+        )
+    }
+
+    private val dismissAlarmCapability =
+        DismissAlarm.CapabilityBuilder()
+            .setExecutionCallback(
+                ExecutionCallback {
+                    sharedPreferences.edit().putBoolean(KEY_ALARM_IS_SET, false).apply()
+                    alarmManager.cancel(alarmIntent)
+                    ExecutionResult.Builder<DismissAlarm.Output>().build()
+                }).build()
+
+    private val snoozeAlarmCapability =
+        SnoozeAlarm.CapabilityBuilder()
+            .setExecutionCallback(
+                ExecutionCallback {
+                    sharedPreferences.edit().putBoolean(KEY_ALARM_IS_SNOOZED, true).apply()
+                    ExecutionResult.Builder<SnoozeAlarm.Output>().build()
+                }).build()
+
+    private val updateAlarmCapability =
+        UpdateAlarm.OverwriteAlarmSchedule.CapabilityBuilder()
+            .setExecutionCallback(
+                ExecutionCallback {
+                    schedule = it.schedule
+                    sharedPreferences.edit().putString(KEY_ALARM_SCHEDULE, schedule.toString())
+                        .apply()
+                    sharedPreferences.edit().putBoolean(KEY_ALARM_IS_SET, true).apply()
+                    setAlarm()
+                    ExecutionResult.Builder<UpdateAlarm.OverwriteAlarmSchedule.Output>().build()
+                }).build()
+
+    override val registeredCapabilities: List<Capability> = listOf(
+        createAlarmCapability,
+        dismissAlarmCapability,
+        snoozeAlarmCapability,
+        updateAlarmCapability,
+    )
+
+    override val allowedApps: List<AppVerificationInfo> = /* TODO */ listOf()
+}
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_alarm.xml b/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_alarm.xml
new file mode 100644
index 0000000..f9cff5d
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_alarm.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_main.xml b/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_main.xml
index 305819b..ff4f005 100644
--- a/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -1,7 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -15,45 +13,7 @@
       android:gravity="center"
       android:orientation="horizontal">
     <TextView
-        android:id="@+id/hour"
-        android:layout_width="50dp"
-        android:layout_height="50dp"
-        android:background="@drawable/round_back_black10_10"
-        android:text="00"
-        android:textColor="#FFFFFF"
-        android:textStyle="bold"
-        android:textSize="30sp"
-        android:gravity="center"/>
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text=":"
-        android:textColor="#FFFFFF"
-        android:layout_marginStart="10dp"
-        android:layout_marginEnd="10dp"
-        android:textSize="30sp"
-        android:textStyle="bold"/>
-    <TextView
-        android:id="@+id/minute"
-        android:layout_width="50dp"
-        android:layout_height="50dp"
-        android:background="@drawable/round_back_black10_10"
-        android:text="00"
-        android:textColor="#FFFFFF"
-        android:textStyle="bold"
-        android:textSize="30sp"
-        android:gravity="center"/>
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text=":"
-        android:textColor="#FFFFFF"
-        android:layout_marginStart="10dp"
-        android:layout_marginEnd="10dp"
-        android:textSize="30sp"
-        android:textStyle="bold"/>
-    <TextView
-        android:id="@+id/second"
+        android:id="@+id/remainingTimeLabel"
         android:layout_width="50dp"
         android:layout_height="50dp"
         android:background="@drawable/round_back_black10_10"
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/xml/shortcuts.xml b/appactions/interaction/integration-tests/testapp/src/main/res/xml/shortcuts.xml
index 446ce53b..11327f3 100644
--- a/appactions/interaction/integration-tests/testapp/src/main/res/xml/shortcuts.xml
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/xml/shortcuts.xml
@@ -31,4 +31,22 @@
                 android:key="timer.duration" />
         </service>
     </capability>
+    <capability android:name="actions.intent.PAUSE_TIMER">
+        <service
+            android:name="androidx.appactions.interaction.testapp.ClockInteractionService"
+            android:identifier="pause_timer_oneshot">
+            <parameter
+                android:name="timer.name"
+                android:key="timer.name" />
+        </service>
+    </capability>
+    <capability android:name="actions.intent.RESUME_TIMER">
+        <service
+            android:name="androidx.appactions.interaction.testapp.ClockInteractionService"
+            android:identifier="resume_timer_oneshot">
+            <parameter
+                android:name="timer.name"
+                android:key="timer.name" />
+        </service>
+    </capability>
 </shortcuts>
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
index 8d5dfd5..e787f76 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
@@ -207,7 +207,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
index ea598ba..bec43e5 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
@@ -95,8 +95,8 @@
                 this.recipientList = recipientList
             }
 
-            fun setMessageText(messageTextList: String): Builder = apply {
-                this.messageText = messageTextList
+            fun setMessageText(messageText: String): Builder = apply {
+                this.messageText = messageText
             }
 
             fun build(): Arguments = Arguments(recipientList, messageText)
@@ -205,7 +205,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecRegistry.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecRegistry.kt
index bebda2f..057aef0 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecRegistry.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecRegistry.kt
@@ -27,17 +27,31 @@
  */
 object ActionSpecRegistry {
   private val argumentsClassToActionSpec = IdentityHashMap<Class<*>, ActionSpec<Any, *>>()
+  private val outputClassToActionSpec = IdentityHashMap<Class<*>, ActionSpec<*, Any>>()
 
   @Suppress("UNCHECKED_CAST")
-  fun <T : Any> registerArgumentsClass(argumentsClass: KClass<T>, actionSpec: ActionSpec<T, *>) {
+  fun <T : Any, R : Any> registerActionSpec(
+    argumentsClass: KClass<T>,
+    outputClass: KClass<R>,
+    actionSpec: ActionSpec<T, R>
+  ) {
     argumentsClassToActionSpec.put(
       argumentsClass.java,
       actionSpec as ActionSpec<Any, *>
     )
+    outputClassToActionSpec.put(
+      outputClass.java,
+      actionSpec as ActionSpec<*, Any>
+    )
   }
 
   @VisibleForTesting
   fun getActionSpecForArguments(arguments: Any): ActionSpec<Any, *>? {
     return argumentsClassToActionSpec[arguments.javaClass]
   }
+
+  @VisibleForTesting
+  fun getActionSpecForOutput(output: Any): ActionSpec<*, Any>? {
+    return outputClassToActionSpec[output.javaClass]
+  }
 }
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecRegistryTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecRegistryTest.kt
index a9bdca6..6248ac0 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecRegistryTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecRegistryTest.kt
@@ -19,6 +19,7 @@
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.testing.spec.Arguments
 import androidx.appactions.interaction.capabilities.core.testing.spec.Output
+import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput.OutputValue
 import androidx.appactions.interaction.proto.ParamValue
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -28,7 +29,7 @@
 @RunWith(JUnit4::class)
 public class ActionSpecRegistryTest {
   @Test
-  fun actionSpecRegistry_retrieveRegisteredSpec() {
+  fun actionSpecRegistry_retrieveRegisteredSpecForArguments() {
     val arguments = Arguments.Builder()
       .setRequiredStringField("a")
       .setRepeatedStringField(listOf("a", "b", "c"))
@@ -49,6 +50,36 @@
     )
   }
 
+  @Test
+  fun actionSpecRegistry_retrieveRegisteredSpecForOutput() {
+    val output = Output.Builder()
+      .setOptionalStringField("a")
+      .setRepeatedStringField(listOf("a", "b", "c"))
+      .build()
+    val actionSpec = ActionSpecRegistry.getActionSpecForOutput(output)!!
+    assertThat(actionSpec.capabilityName).isEqualTo("actions.intent.TEST")
+    assertThat(
+      actionSpec.convertOutputToProto(output).getOutputValuesList()
+    ).containsExactly(
+      OutputValue.newBuilder()
+        .setName("repeatedStringOutput")
+        .addValues(
+          ParamValue.newBuilder().setStringValue("a")
+        )
+        .addValues(
+          ParamValue.newBuilder().setStringValue("b")
+        )
+        .addValues(
+          ParamValue.newBuilder().setStringValue("c")
+        ).build(),
+      OutputValue.newBuilder()
+        .setName("optionalStringOutput")
+        .addValues(
+          ParamValue.newBuilder().setStringValue("a")
+        ).build()
+    )
+  }
+
   companion object {
     val ACTION_SPEC: ActionSpec<Arguments, Output> =
       ActionSpecBuilder.ofCapabilityNamed("actions.intent.TEST")
@@ -79,7 +110,11 @@
           TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue)
         .build()
     init {
-      ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+      ActionSpecRegistry.registerActionSpec(
+        Arguments::class,
+        Output::class,
+        ACTION_SPEC
+      )
     }
   }
 }
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
index 0357c11..906d4fe 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
@@ -122,7 +122,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
index e91e7f8..dbbbe38 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
@@ -127,7 +127,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
index 52e70bb1b..62b7372 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
@@ -101,7 +101,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
index 41e651f..3d028c5 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
@@ -101,7 +101,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
index fed89a1..416af07 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
@@ -123,7 +123,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
index 3b13c995..4568b3c 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
@@ -101,7 +101,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarm.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarm.kt
index c624ef35c..f783e57 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarm.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarm.kt
@@ -249,7 +249,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarm.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarm.kt
index b32f53c..b649310 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarm.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarm.kt
@@ -159,7 +159,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/GetAlarm.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/GetAlarm.kt
new file mode 100644
index 0000000..344b3bb
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/GetAlarm.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.productivity
+
+import androidx.appactions.builtintypes.types.Alarm
+import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
+import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
+import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecRegistry
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.serializers.types.ALARM_TYPE_SPEC
+
+@CapabilityFactory(name = GetAlarm.CAPABILITY_NAME)
+class GetAlarm private constructor() {
+    internal enum class SlotMetadata(val path: String) {
+        ALARM("alarm")
+    }
+
+    class CapabilityBuilder : Capability.Builder<
+        CapabilityBuilder,
+        Arguments,
+        Output,
+        Confirmation,
+        ExecutionSession
+        >(ACTION_SPEC) {
+            fun setAlarmProperty(alarm: Property<Alarm>) = setProperty(
+                SlotMetadata.ALARM.path,
+                alarm,
+                EntityConverter.of(ALARM_TYPE_SPEC)
+            )
+        }
+
+    class Arguments internal constructor(
+        val alarm: AlarmReference?
+    ) {
+        override fun toString(): String {
+            return "Arguments(alarm=$alarm)"
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+
+            other as Arguments
+
+            if (alarm != other.alarm) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            return alarm.hashCode()
+        }
+
+        class Builder {
+            private var alarm: AlarmReference? = null
+
+            fun setAlarm(alarm: AlarmReference): Builder = apply { this.alarm = alarm }
+
+            fun build(): Arguments = Arguments(alarm)
+        }
+    }
+
+    class Output internal constructor()
+    sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+    class Confirmation internal constructor()
+
+    companion object {
+        /** Canonical name for [GetAlarm] capability */
+        const val CAPABILITY_NAME = "actions.intent.GET_ALARM"
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
+                .setOutput(Output::class.java)
+                .bindParameter(
+                    SlotMetadata.ALARM.path,
+                    Arguments::alarm,
+                    Arguments.Builder::setAlarm,
+                    AlarmReference.PARAM_VALUE_CONVERTER
+                )
+                .build()
+        init {
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
+        }
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
index 50169ec..dc79ff55 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
@@ -178,7 +178,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
index b6a50ac..dba0167 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
@@ -175,7 +175,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
index 44ab4d30..c80f509 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
@@ -175,7 +175,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarm.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarm.kt
index 5235697..ab6c117 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarm.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarm.kt
@@ -202,7 +202,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
index b0d3da6..5812afd 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
@@ -214,7 +214,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
index f776b11..876a07a 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
@@ -175,7 +175,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/UpdateAlarm.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/UpdateAlarm.kt
index 0b5fdbb..82b6e40 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/UpdateAlarm.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/UpdateAlarm.kt
@@ -291,7 +291,7 @@
                     )
                     .build()
             init {
-                ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+                ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
             }
         }
     }
@@ -545,7 +545,7 @@
                     )
                     .build()
             init {
-                ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+                ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
             }
         }
     }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/UpdateTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/UpdateTimer.kt
index c02e553..36f4a06 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/UpdateTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/UpdateTimer.kt
@@ -292,7 +292,7 @@
                     )
                     .build()
             init {
-                ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+                ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
             }
         }
     }
@@ -547,7 +547,7 @@
                     )
                     .build()
             init {
-                ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+                ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
             }
         }
     }
@@ -798,7 +798,7 @@
                     )
                     .build()
             init {
-                ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+                ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
             }
         }
     }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/GetAlarmTest.kt b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/GetAlarmTest.kt
new file mode 100644
index 0000000..ffb0700
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/GetAlarmTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.productivity
+
+import android.util.SizeF
+import androidx.appactions.builtintypes.types.Alarm
+import androidx.appactions.interaction.capabilities.core.ExecutionCallback
+import androidx.appactions.interaction.capabilities.core.ExecutionResult
+import androidx.appactions.interaction.capabilities.core.HostProperties
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.productivity.GetAlarm.Arguments
+import androidx.appactions.interaction.capabilities.productivity.GetAlarm.Output
+import androidx.appactions.interaction.capabilities.testing.internal.ArgumentUtils
+import androidx.appactions.interaction.capabilities.testing.internal.FakeCallbackInternal
+import androidx.appactions.interaction.capabilities.testing.internal.TestingUtils.awaitSync
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction
+import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.proto.TaskInfo
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CompletableDeferred
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class GetAlarmTest {
+    private val hostProperties =
+        HostProperties.Builder().setMaxHostSizeDp(SizeF(300f, 500f)).build()
+
+    @Test
+    fun createCapability_expectedResult() {
+        val argsDeferred = CompletableDeferred<Arguments>()
+        val capability =
+            GetAlarm.CapabilityBuilder()
+                .setId("get alarm")
+                .setAlarmProperty(Property<Alarm>(isRequiredForExecution = true))
+                .setExecutionCallback(
+                    ExecutionCallback {
+                        argsDeferred.complete(it)
+                        ExecutionResult.Builder<Output>().build()
+                    }
+                )
+                .build()
+        val capabilitySession = capability.createSession("fakeSessionId", hostProperties)
+        val args: MutableMap<String, ParamValue> = mutableMapOf(
+            "alarm" to ParamValue.newBuilder()
+                .setStructValue(
+                    Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("Alarm").build())
+                        .putFields("identifier", Value.newBuilder().setStringValue("abc").build())
+                )
+                .build()
+        )
+        capabilitySession.execute(ArgumentUtils.buildArgs(args), FakeCallbackInternal())
+
+        assertThat(capability.appAction)
+            .isEqualTo(
+                AppAction.newBuilder()
+                    .setIdentifier("get alarm")
+                    .setName("actions.intent.GET_ALARM")
+                    .addParams(IntentParameter.newBuilder().setName("alarm").setIsRequired(true))
+                    .setTaskInfo(TaskInfo.getDefaultInstance())
+                    .build()
+            )
+        assertThat(argsDeferred.awaitSync())
+            .isEqualTo(
+                Arguments.Builder()
+                    .setAlarm(AlarmReference(Alarm.Builder().setIdentifier("abc").build()))
+                    .build()
+            )
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt
index 3cf025b..05a4757 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt
@@ -161,7 +161,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
index 2f90017..4081fb3 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
@@ -254,7 +254,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt
index a603fe3..b2cf5d4 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt
@@ -161,7 +161,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt
index 1454dde..bd4795e 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt
@@ -157,7 +157,7 @@
                 )
                 .build()
         init {
-            ActionSpecRegistry.registerArgumentsClass(Arguments::class, ACTION_SPEC)
+            ActionSpecRegistry.registerActionSpec(Arguments::class, Output::class, ACTION_SPEC)
         }
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-testing/build.gradle b/appactions/interaction/interaction-capabilities-testing/build.gradle
index bf9b569..0d0627e 100644
--- a/appactions/interaction/interaction-capabilities-testing/build.gradle
+++ b/appactions/interaction/interaction-capabilities-testing/build.gradle
@@ -29,6 +29,12 @@
     implementation("androidx.annotation:annotation:1.1.0")
     implementation("androidx.concurrent:concurrent-futures:1.1.0")
     implementation(project(":appactions:interaction:interaction-capabilities-core"))
+
+    // depending on productivity (any vertical works) capability artifact for writing unit tests
+    // for this library
+    testImplementation(project(":appactions:interaction:interaction-capabilities-productivity"))
+    testImplementation(libs.junit)
+    testImplementation(libs.truth)
 }
 
 android {
diff --git a/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/CapabilityRequest.kt b/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/CapabilityRequest.kt
new file mode 100644
index 0000000..4dcf208
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/CapabilityRequest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.testing
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
+import androidx.appactions.interaction.proto.FulfillmentRequest
+
+/**
+ * Represents a request which can be sent to some capability session for unit testing.
+ */
+@VisibleForTesting
+class CapabilityRequest @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+constructor(
+  @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+  val fulfillmentRequest: FulfillmentRequest
+) {
+  /**
+   * Returns a new CapabilityRequest that sets an identifier to the first Fulfillment in this
+   * request.
+   * Fulfillments after the first are dropped.
+   */
+  @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+  fun addCapabilityIdentifier(capabilityIdentifier: String): CapabilityRequest {
+    val fulfillment = fulfillmentRequest.getFulfillments(0)
+    return CapabilityRequest(
+      FulfillmentRequest.newBuilder().addFulfillments(
+        fulfillment.toBuilder().setIdentifier(capabilityIdentifier).build()
+      ).build()
+    )
+  }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/CapabilityResponse.kt b/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/CapabilityResponse.kt
new file mode 100644
index 0000000..17b40dc
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/CapabilityResponse.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.testing
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
+import androidx.appactions.interaction.proto.FulfillmentResponse
+
+/**
+ * Represents a request which can be sent to some capability session for unit testing.
+ */
+@VisibleForTesting
+class CapabilityResponse @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+constructor(
+  @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+  val fulfillmentResponse: FulfillmentResponse
+)
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/CapabilityTestUtils.kt b/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/CapabilityTestUtils.kt
new file mode 100644
index 0000000..b5f2589
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/CapabilityTestUtils.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.testing
+
+import androidx.annotation.VisibleForTesting
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecRegistry
+import androidx.appactions.interaction.proto.FulfillmentRequest
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentParam
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentValue
+
+/**
+ * Contains utility methods and classes for testing any BII capability built with the
+ * AppInteraction Capabilities library.
+ */
+@VisibleForTesting
+object CapabilityTestUtils {
+  /**
+   * Create a CapabilityRequest for executing a capability with some Argument instance.
+   * @param args an Arguments instance for some AppInteraction capability.
+   * @throws [IllegalArgumentException] if [args] is not an instance of a supported Arguments
+   * class.
+   * Example Usage:
+   * ```
+   * val arguments = CreateMessage.Arguments.Builder().setMessageText("Hello").build()
+   * val capabilityRequest = createCapabilityRequest(arguments)
+   * ```
+   */
+  fun createCapabilityRequest(args: Any): CapabilityRequest {
+    val actionSpec = ActionSpecRegistry.getActionSpecForArguments(args)
+    if (actionSpec == null) {
+      throw IllegalArgumentException(
+        "Failed to find associated capability class for arguments of class ${args.javaClass}."
+      )
+    }
+    val fulfillmentParams = actionSpec.serializeArguments(args).map {
+      (slotName, paramValues) ->
+      FulfillmentParam.newBuilder().setName(slotName).addAllFulfillmentValues(
+        paramValues.map { FulfillmentValue.newBuilder().setValue(it).build() }
+      ).build()
+    }
+    return CapabilityRequest(
+      FulfillmentRequest.newBuilder().addFulfillments(
+        Fulfillment.newBuilder()
+          .setName(actionSpec.capabilityName)
+          .addAllParams(fulfillmentParams)
+          .build()
+      ).build()
+    )
+  }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/package-info.java b/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/package-info.java
new file mode 100644
index 0000000..fd06784
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-testing/src/main/java/androidx/appactions/interaction/capabilities/testing/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+package androidx.appactions.interaction.capabilities.testing;
+
+import androidx.annotation.RestrictTo;
diff --git a/appactions/interaction/interaction-capabilities-testing/src/test/java/androidx/appactions/interaction/capabilities/testing/CapabilityTestUtilsTest.kt b/appactions/interaction/interaction-capabilities-testing/src/test/java/androidx/appactions/interaction/capabilities/testing/CapabilityTestUtilsTest.kt
new file mode 100644
index 0000000..1efb0e3
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-testing/src/test/java/androidx/appactions/interaction/capabilities/testing/CapabilityTestUtilsTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.testing
+
+import androidx.appactions.interaction.capabilities.core.ExecutionCallback
+import androidx.appactions.interaction.capabilities.core.ExecutionResult
+import androidx.appactions.interaction.capabilities.productivity.StartTimer
+import androidx.appactions.interaction.proto.FulfillmentRequest
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentParam
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentValue
+import androidx.appactions.interaction.proto.ParamValue
+import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class CapabilityTestUtilsTest {
+  // triggers StartTimer companion object init
+  val unusedCapability = StartTimer.CapabilityBuilder()
+    .setId("unusedStartTimer")
+    .setExecutionCallback(ExecutionCallback {
+        ExecutionResult.Builder<StartTimer.Output>().build()
+    })
+    .build()
+
+  @Test
+  fun invalidArgs_throwsException() {
+    val thrown = assertThrows(IllegalArgumentException::class.java, {
+      CapabilityTestUtils.createCapabilityRequest(Any())
+    })
+    assertThat(thrown).hasMessageThat().isEqualTo(
+      "Failed to find associated capability class for arguments of class class java.lang.Object."
+    )
+  }
+
+  @Test
+  fun startTimerArgs_serializesArgs() {
+    val startTimerArgs = StartTimer.Arguments.Builder()
+      .setIdentifier("timerId")
+      .setName("my timer")
+      .setDuration(Duration.ofDays(3))
+      .build()
+
+    val fulfillmentRequest = CapabilityTestUtils.createCapabilityRequest(
+      startTimerArgs
+    ).addCapabilityIdentifier("unusedStartTimer").fulfillmentRequest
+
+    assertThat(fulfillmentRequest).isEqualTo(
+      FulfillmentRequest.newBuilder()
+        .addFulfillments(
+          Fulfillment.newBuilder()
+            .setName(StartTimer.CAPABILITY_NAME)
+            .setIdentifier("unusedStartTimer")
+            .addParams(
+              FulfillmentParam.newBuilder().setName("timer.identifier").addFulfillmentValues(
+                FulfillmentValue.newBuilder().setValue(
+                  ParamValue.newBuilder().setStringValue("timerId")
+                )
+              )
+            )
+            .addParams(
+              FulfillmentParam.newBuilder().setName("timer.name").addFulfillmentValues(
+                FulfillmentValue.newBuilder().setValue(
+                  ParamValue.newBuilder().setStringValue("my timer")
+                )
+              )
+            )
+            .addParams(
+              FulfillmentParam.newBuilder().setName("timer.duration").addFulfillmentValues(
+                FulfillmentValue.newBuilder().setValue(
+                  ParamValue.newBuilder().setStringValue("PT72H")
+                )
+              )
+            )
+        )
+        .build()
+    )
+  }
+}
diff --git a/appactions/interaction/interaction-proto/src/main/proto/app_actions_data.proto b/appactions/interaction/interaction-proto/src/main/proto/app_actions_data.proto
index dfae0d4..4e2e9dd 100644
--- a/appactions/interaction/interaction-proto/src/main/proto/app_actions_data.proto
+++ b/appactions/interaction/interaction-proto/src/main/proto/app_actions_data.proto
@@ -314,7 +314,7 @@
       INTENT_CONFIRMED = 3;
     }
 
-    // The current progress of handling the dialog.
+    // The Type of this Fulfillment. Not applicable for one-shot Fulfillment.
     optional Type type = 5;
 
     // The status of the SYNC request. Only set when fulfillment type == SYNC.
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 0f110a8..a8b7765 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -110,4 +110,8 @@
     description = "Provides backwards-compatible implementations of UI-related Android SDK " +
             "functionality, including dark mode and Material theming."
     failOnDeprecationWarnings = false
+    deviceTests {
+        // Temporarily disabled due to b/286161632
+        enabled = false
+    }
 }
diff --git a/appcompat/appcompat/src/main/res/values-kn/strings.xml b/appcompat/appcompat/src/main/res/values-kn/strings.xml
index 40454c7..edf8c0b 100644
--- a/appcompat/appcompat/src/main/res/values-kn/strings.xml
+++ b/appcompat/appcompat/src/main/res/values-kn/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="abc_action_mode_done" msgid="4692188335987374352">"ಮುಗಿದಿದೆ"</string>
+    <string name="abc_action_mode_done" msgid="4692188335987374352">"ಆಯಿತು"</string>
     <string name="abc_action_bar_home_description" msgid="5976598919945601918">"ಹೋಮ್‌ಗೆ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
     <string name="abc_action_bar_up_description" msgid="8388173803310557296">"ಮೇಲಕ್ಕೆ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
     <string name="abc_action_menu_overflow_description" msgid="3937310113216875497">"ಇನ್ನಷ್ಟು ಆಯ್ಕೆಗಳು"</string>
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
index daf7d3a..5413e66 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
@@ -160,7 +160,9 @@
                         .build()
                 ).build();
         SchemaTypeConfigProto schemaTypeConfigProto3 = SchemaTypeConfigProto.newBuilder()
-                .setSchemaType("RefType").build();
+                .setSchemaType("RefType")
+                .addParentTypes("Foo")
+                .build();
         SchemaProto newSchema = SchemaProto.newBuilder()
                 .addTypes(schemaTypeConfigProto1)
                 .addTypes(schemaTypeConfigProto2)
@@ -209,7 +211,9 @@
                                 .build()
                         ).build())
                 .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("package$newDatabase/RefType").build())
+                        .setSchemaType("package$newDatabase/RefType")
+                        .addParentTypes("package$newDatabase/Foo")
+                        .build())
                 .build();
 
         existingTypes.addAll(expectedSchema.getTypesList());
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java
index 3e5a91b..e848cc2 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java
@@ -159,4 +159,32 @@
         assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedAlbumProto))
                 .isEqualTo(albumSchema);
     }
+
+    @Test
+    public void testGetProto_ParentTypes() {
+        AppSearchSchema schema = new AppSearchSchema.Builder("EmailMessage")
+                .addParentType("Email")
+                .addParentType("Message")
+                .build();
+
+        SchemaTypeConfigProto expectedSchemaProto = SchemaTypeConfigProto.newBuilder()
+                .setSchemaType("EmailMessage")
+                .setVersion(12345)
+                .addParentTypes("Email")
+                .addParentTypes("Message")
+                .build();
+        SchemaTypeConfigProto alternativeExpectedSchemaProto = SchemaTypeConfigProto.newBuilder()
+                .setSchemaType("EmailMessage")
+                .setVersion(12345)
+                .addParentTypes("Message")
+                .addParentTypes("Email")
+                .build();
+
+        assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(schema, /*version=*/12345))
+                .isAnyOf(expectedSchemaProto, alternativeExpectedSchemaProto);
+        assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedSchemaProto))
+                .isEqualTo(schema);
+        assertThat(SchemaToProtoConverter.toAppSearchSchema(alternativeExpectedSchemaProto))
+                .isEqualTo(schema);
+    }
 }
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
index 644fc10..bad10a1 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
@@ -63,6 +63,8 @@
             case Features.SCHEMA_SET_DELETION_PROPAGATION:
                 // fall through
             case Features.SET_SCHEMA_CIRCULAR_REFERENCES:
+                // fall through
+            case Features.SCHEMA_ADD_PARENT_TYPE:
                 return true;
             default:
                 return false;
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
index 24c76ca..f9f326e 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
@@ -2436,6 +2436,13 @@
                 }
             }
 
+            // Rewrite SchemaProto.types.parent_types
+            for (int parentTypeIdx = 0; parentTypeIdx < typeConfigBuilder.getParentTypesCount();
+                    parentTypeIdx++) {
+                String newParentType = prefix + typeConfigBuilder.getParentTypes(parentTypeIdx);
+                typeConfigBuilder.setParentTypes(parentTypeIdx, newParentType);
+            }
+
             newTypesToProto.put(newSchemaType, typeConfigBuilder.build());
         }
 
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java
index 0e64163..bf603a0 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java
@@ -48,6 +48,7 @@
      * Converts an {@link androidx.appsearch.app.AppSearchSchema} into a
      * {@link SchemaTypeConfigProto}.
      */
+    // TODO(b/284356266): Consider handling addition of schema name prefixes in this function.
     @NonNull
     public static SchemaTypeConfigProto toSchemaTypeConfigProto(@NonNull AppSearchSchema schema,
             int version) {
@@ -60,6 +61,7 @@
             PropertyConfigProto propertyProto = toPropertyConfigProto(properties.get(i));
             protoBuilder.addProperties(propertyProto);
         }
+        protoBuilder.addAllParentTypes(schema.getParentTypes());
         return protoBuilder.build();
     }
 
@@ -139,6 +141,7 @@
      * Converts a {@link SchemaTypeConfigProto} into an
      * {@link androidx.appsearch.app.AppSearchSchema}.
      */
+    // TODO(b/284356266): Consider handling removal of schema name prefixes in this function.
     @NonNull
     public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) {
         Preconditions.checkNotNull(proto);
@@ -149,6 +152,10 @@
             AppSearchSchema.PropertyConfig propertyConfig = toPropertyConfig(properties.get(i));
             builder.addProperty(propertyConfig);
         }
+        List<String> parentTypes = proto.getParentTypesList();
+        for (int i = 0; i < parentTypes.size(); i++) {
+            builder.addParentType(parentTypes.get(i));
+        }
         return builder.build();
     }
 
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/util/PrefixUtil.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/util/PrefixUtil.java
index 89183f1..f9b9f5f 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/util/PrefixUtil.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/util/PrefixUtil.java
@@ -267,6 +267,14 @@
                 typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
             }
         }
+
+        // Rewrite SchemaProto.types.parent_types
+        for (int parentTypeIdx = 0; parentTypeIdx < typeConfigBuilder.getParentTypesCount();
+                parentTypeIdx++) {
+            String newParentType = typeConfigBuilder.getParentTypes(parentTypeIdx).substring(
+                    typePrefix.length());
+            typeConfigBuilder.setParentTypes(parentTypeIdx, newParentType);
+        }
         return typePrefix;
     }
 }
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
index 48c4a66..793a64c 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
@@ -73,6 +73,9 @@
                 // fall through
             case Features.SCHEMA_SET_DELETION_PROPAGATION:
                 // TODO(b/268521214) : Update when feature is ready in service-appsearch.
+                // fall through
+            case Features.SCHEMA_ADD_PARENT_TYPE:
+                // TODO(b/269295094) : Update when feature is ready in service-appsearch.
                 return false;
             default:
                 return false;
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SchemaToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SchemaToPlatformConverter.java
index edc2141..6ecf547 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SchemaToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SchemaToPlatformConverter.java
@@ -24,6 +24,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.Features;
 import androidx.core.os.BuildCompat;
 import androidx.core.util.Preconditions;
 
@@ -58,6 +59,11 @@
                     toPlatformProperty(properties.get(i));
             platformBuilder.addProperty(platformProperty);
         }
+        if (!jetpackSchema.getParentTypes().isEmpty()) {
+            // TODO(b/269295094): Remove this once polymorphism becomes available.
+            throw new UnsupportedOperationException(Features.SCHEMA_ADD_PARENT_TYPE
+                    + " is not available on this AppSearch implementation.");
+        }
         return platformBuilder.build();
     }
 
@@ -80,6 +86,8 @@
             AppSearchSchema.PropertyConfig jetpackProperty = toJetpackProperty(properties.get(i));
             jetpackBuilder.addProperty(jetpackProperty);
         }
+        // TODO(b/269295094): Call jetpackBuilder.addParentType() to add parent types once
+        //  polymorphism becomes available in platform.
         return jetpackBuilder.build();
     }
 
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 61fa4ed..8d2e61c 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -3,6 +3,7 @@
 
   @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Document {
     method public abstract String name() default "";
+    method public abstract Class<?>[] parent() default {};
   }
 
   @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.BooleanProperty {
@@ -94,6 +95,7 @@
   }
 
   public final class AppSearchSchema {
+    method public java.util.List<java.lang.String!> getParentTypes();
     method public java.util.List<androidx.appsearch.app.AppSearchSchema.PropertyConfig!> getProperties();
     method public String getSchemaType();
   }
@@ -109,6 +111,7 @@
 
   public static final class AppSearchSchema.Builder {
     ctor public AppSearchSchema.Builder(String);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_PARENT_TYPE) public androidx.appsearch.app.AppSearchSchema.Builder addParentType(String);
     method public androidx.appsearch.app.AppSearchSchema.Builder addProperty(androidx.appsearch.app.AppSearchSchema.PropertyConfig);
     method public androidx.appsearch.app.AppSearchSchema build();
   }
@@ -209,7 +212,7 @@
 
   public interface DocumentClassFactory<T> {
     method public T fromGenericDocument(androidx.appsearch.app.GenericDocument) throws androidx.appsearch.exceptions.AppSearchException;
-    method public java.util.List<java.lang.Class<?>!> getNestedDocumentClasses() throws androidx.appsearch.exceptions.AppSearchException;
+    method public java.util.List<java.lang.Class<?>!> getDependencyDocumentClasses() throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.AppSearchSchema getSchema() throws androidx.appsearch.exceptions.AppSearchException;
     method public String getSchemaName();
     method public androidx.appsearch.app.GenericDocument toGenericDocument(T) throws androidx.appsearch.exceptions.AppSearchException;
@@ -224,6 +227,7 @@
     field public static final String JOIN_SPEC_AND_QUALIFIED_ID = "JOIN_SPEC_AND_QUALIFIED_ID";
     field public static final String LIST_FILTER_QUERY_LANGUAGE = "LIST_FILTER_QUERY_LANGUAGE";
     field public static final String NUMERIC_SEARCH = "NUMERIC_SEARCH";
+    field public static final String SCHEMA_ADD_PARENT_TYPE = "SCHEMA_ADD_PARENT_TYPE";
     field public static final String SCHEMA_SET_DELETION_PROPAGATION = "SCHEMA_SET_DELETION_PROPAGATION";
     field public static final String SEARCH_RESULT_MATCH_INFO_SUBMATCH = "SEARCH_RESULT_MATCH_INFO_SUBMATCH";
     field public static final String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 61fa4ed..8d2e61c 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -3,6 +3,7 @@
 
   @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Document {
     method public abstract String name() default "";
+    method public abstract Class<?>[] parent() default {};
   }
 
   @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.BooleanProperty {
@@ -94,6 +95,7 @@
   }
 
   public final class AppSearchSchema {
+    method public java.util.List<java.lang.String!> getParentTypes();
     method public java.util.List<androidx.appsearch.app.AppSearchSchema.PropertyConfig!> getProperties();
     method public String getSchemaType();
   }
@@ -109,6 +111,7 @@
 
   public static final class AppSearchSchema.Builder {
     ctor public AppSearchSchema.Builder(String);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_ADD_PARENT_TYPE) public androidx.appsearch.app.AppSearchSchema.Builder addParentType(String);
     method public androidx.appsearch.app.AppSearchSchema.Builder addProperty(androidx.appsearch.app.AppSearchSchema.PropertyConfig);
     method public androidx.appsearch.app.AppSearchSchema build();
   }
@@ -209,7 +212,7 @@
 
   public interface DocumentClassFactory<T> {
     method public T fromGenericDocument(androidx.appsearch.app.GenericDocument) throws androidx.appsearch.exceptions.AppSearchException;
-    method public java.util.List<java.lang.Class<?>!> getNestedDocumentClasses() throws androidx.appsearch.exceptions.AppSearchException;
+    method public java.util.List<java.lang.Class<?>!> getDependencyDocumentClasses() throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.AppSearchSchema getSchema() throws androidx.appsearch.exceptions.AppSearchException;
     method public String getSchemaName();
     method public androidx.appsearch.app.GenericDocument toGenericDocument(T) throws androidx.appsearch.exceptions.AppSearchException;
@@ -224,6 +227,7 @@
     field public static final String JOIN_SPEC_AND_QUALIFIED_ID = "JOIN_SPEC_AND_QUALIFIED_ID";
     field public static final String LIST_FILTER_QUERY_LANGUAGE = "LIST_FILTER_QUERY_LANGUAGE";
     field public static final String NUMERIC_SEARCH = "NUMERIC_SEARCH";
+    field public static final String SCHEMA_ADD_PARENT_TYPE = "SCHEMA_ADD_PARENT_TYPE";
     field public static final String SCHEMA_SET_DELETION_PROPAGATION = "SCHEMA_SET_DELETION_PROPAGATION";
     field public static final String SEARCH_RESULT_MATCH_INFO_SUBMATCH = "SEARCH_RESULT_MATCH_INFO_SUBMATCH";
     field public static final String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
index 1f25e64..83d9dcea 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
@@ -686,4 +686,497 @@
         mSession.setSchemaAsync(request).get();
         assertThat(mSession.getSchemaAsync().get().getSchemas()).hasSize(3);
     }
+
+    @Document
+    static class Root {
+        @Document.Id String mId;
+        @Document.Namespace String mNamespace;
+    }
+
+    @Document(name = "Email", parent = Root.class)
+    static class Email extends Root {
+        @Document.StringProperty String mSender;
+    }
+
+    @Document(name = "Message", parent = Root.class)
+    static class Message extends Root {
+        @Document.StringProperty String mContent;
+    }
+
+    // EmailMessage can choose any class to "extends" from, since Java's type relationship is
+    // independent on AppSearch's. In this case, EmailMessage extends Root to avoid redefining
+    // mId and mNamespace, but it still needs to specify mSender and mContent coming from
+    // Email and Message.
+    @Document(name = "EmailMessage", parent = {Email.class, Message.class})
+    static class EmailMessage extends Root {
+        @Document.StringProperty String mSender;
+        @Document.StringProperty String mContent;
+    }
+
+    @Test
+    public void testPolymorphism() throws Exception {
+        assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+        mSession.setSchemaAsync(new SetSchemaRequest.Builder()
+                // EmailMessage's dependencies should be automatically added.
+                .addDocumentClasses(EmailMessage.class)
+                // Add some other class
+                .addDocumentClasses(Gift.class)
+                .build()).get();
+
+        // Create documents
+        Root root = new Root();
+        root.mNamespace = "namespace";
+        root.mId = "id1";
+
+        Email email = new Email();
+        email.mNamespace = "namespace";
+        email.mId = "id2";
+        email.mSender = "test@test.com";
+
+        Message message = new Message();
+        message.mNamespace = "namespace";
+        message.mId = "id3";
+        message.mContent = "hello";
+
+        EmailMessage emailMessage = new EmailMessage();
+        emailMessage.mNamespace = "namespace";
+        emailMessage.mId = "id4";
+        emailMessage.mSender = "test@test.com";
+        emailMessage.mContent = "hello";
+
+        Gift gift = new Gift();
+        gift.mNamespace = "namespace";
+        gift.mId = "id5";
+
+        checkIsBatchResultSuccess(mSession.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addDocuments(root, email, message, emailMessage, gift)
+                        .build()));
+
+        // Query for all documents
+        SearchResults searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(5);
+
+        // A query with a filter for the "Root" type should also include "Email", "Message" and
+        // "EmailMessage".
+        searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterDocumentClasses(Root.class)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(4);
+
+        // A query with a filter for the "Email" type should also include "EmailMessage".
+        searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterDocumentClasses(Email.class)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+
+        // A query with a filter for the "Message" type should also include "EmailMessage".
+        searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterDocumentClasses(Message.class)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+
+        // Query with a filter for the "EmailMessage" type.
+        searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterDocumentClasses(EmailMessage.class)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+    }
+
+    // A class that some properties are annotated via getters without backing fields.
+    @Document
+    static class FakeMessage {
+        public int mSenderSetCount = 0;
+        public String mContent;
+
+        @Document.Id String mId;
+        @Document.Namespace String mNamespace;
+
+        @Document.StringProperty
+        String getSender() {
+            return "fake sender";
+        }
+
+        @Document.StringProperty
+        String getContent() {
+            return mContent;
+        }
+
+        @Document.StringProperty String mNote;
+
+        void setSender(String sender) {
+            if (sender.equals("fake sender")) {
+                mSenderSetCount += 1;
+            }
+        }
+
+        FakeMessage(String id, String namespace, String content) {
+            mId = id;
+            mNamespace = namespace;
+            mContent = content;
+        }
+    }
+
+    @Test
+    public void testGenericDocumentConversion_AnnotatedGetter() throws Exception {
+        // Create a document
+        FakeMessage fakeMessage = new FakeMessage("id", "namespace", "fake content");
+        fakeMessage.setSender("fake sender");
+        fakeMessage.mNote = "fake note";
+
+        // Test the conversion from FakeMessage to GenericDocument
+        GenericDocument genericDocument = GenericDocument.fromDocumentClass(fakeMessage);
+        assertThat(genericDocument.getId()).isEqualTo("id");
+        assertThat(genericDocument.getNamespace()).isEqualTo("namespace");
+        assertThat(genericDocument.getSchemaType()).isEqualTo("FakeMessage");
+        assertThat(genericDocument.getPropertyString("sender")).isEqualTo("fake sender");
+        assertThat(genericDocument.getPropertyString("content")).isEqualTo("fake content");
+        assertThat(genericDocument.getPropertyString("note")).isEqualTo("fake note");
+
+
+        // Test the conversion from GenericDocument to FakeMessage
+        FakeMessage newFakeMessage = genericDocument.toDocumentClass(FakeMessage.class);
+        assertThat(newFakeMessage.mId).isEqualTo("id");
+        assertThat(newFakeMessage.mNamespace).isEqualTo("namespace");
+        assertThat(newFakeMessage.mSenderSetCount).isEqualTo(1);
+        assertThat(newFakeMessage.getContent()).isEqualTo("fake content");
+        assertThat(newFakeMessage.mNote).isEqualTo("fake note");
+    }
+
+    @Document
+    interface InterfaceRoot {
+        @Document.Id
+        String getId();
+
+        @Document.Namespace
+        String getNamespace();
+
+        @Document.CreationTimestampMillis
+        long getCreationTimestamp();
+
+        static InterfaceRoot create(String id, String namespace, long creationTimestamp) {
+            return new InterfaceRootImpl(id, namespace, creationTimestamp);
+        }
+    }
+
+    static class InterfaceRootImpl implements InterfaceRoot {
+        String mId;
+        String mNamespace;
+        long mCreationTimestamp;
+
+        InterfaceRootImpl(String id, String namespace, long creationTimestamp) {
+            mId = id;
+            mNamespace = namespace;
+            mCreationTimestamp = creationTimestamp;
+        }
+
+        public String getId() {
+            return mId;
+        }
+
+        public String getNamespace() {
+            return mNamespace;
+        }
+
+        public long getCreationTimestamp() {
+            return mCreationTimestamp;
+        }
+    }
+
+    @Document(name = "Place", parent = InterfaceRoot.class)
+    interface Place extends InterfaceRoot {
+        @Document.StringProperty
+        String getLocation();
+
+        static Place createPlace(String id, String namespace, long creationTimestamp,
+                String location) {
+            return new PlaceImpl(id, namespace, creationTimestamp, location);
+        }
+    }
+
+    static class PlaceImpl implements Place {
+        String mId;
+        String mNamespace;
+        String mLocation;
+        long mCreationTimestamp;
+
+        PlaceImpl(String id, String namespace, long creationTimestamp, String location) {
+            mId = id;
+            mNamespace = namespace;
+            mCreationTimestamp = creationTimestamp;
+            mLocation = location;
+        }
+
+        public String getId() {
+            return mId;
+        }
+
+        public String getNamespace() {
+            return mNamespace;
+        }
+
+        public long getCreationTimestamp() {
+            return mCreationTimestamp;
+        }
+
+        public String getLocation() {
+            return mLocation;
+        }
+    }
+
+    @Document(name = "Organization", parent = InterfaceRoot.class)
+    interface Organization extends InterfaceRoot {
+        @Document.StringProperty
+        String getOrganizationDescription();
+
+        static Organization createOrganization(String id, String namespace, long creationTimestamp,
+                String organizationDescription) {
+            return new OrganizationImpl(id, namespace, creationTimestamp, organizationDescription);
+        }
+    }
+
+    static class OrganizationImpl implements Organization {
+        String mId;
+        String mNamespace;
+        long mCreationTimestamp;
+        String mOrganizationDescription;
+
+        OrganizationImpl(String id, String namespace, long creationTimestamp,
+                String organizationDescription) {
+            mId = id;
+            mNamespace = namespace;
+            mCreationTimestamp = creationTimestamp;
+            mOrganizationDescription = organizationDescription;
+        }
+
+        public String getId() {
+            return mId;
+        }
+
+        public String getNamespace() {
+            return mNamespace;
+        }
+
+        public long getCreationTimestamp() {
+            return mCreationTimestamp;
+        }
+
+        public String getOrganizationDescription() {
+            return mOrganizationDescription;
+        }
+    }
+
+    @Document(name = "Business", parent = {Place.class, Organization.class})
+    interface Business extends Place, Organization {
+        @Document.StringProperty
+        String getBusinessName();
+
+        static Business createBusiness(String id, String namespace, long creationTimestamp,
+                String location, String organizationDescription, String businessName) {
+            return new BusinessImpl(id, namespace, creationTimestamp, location,
+                    organizationDescription, businessName);
+        }
+    }
+
+    // We have to annotate this class with @Document to generate a factory for it. Otherwise there
+    // will be an ambiguity on finding the factory class.
+    @Document(name = "BusinessImpl", parent = Business.class)
+    static class BusinessImpl extends PlaceImpl implements Business {
+        String mOrganizationDescription;
+        String mBusinessName;
+
+        BusinessImpl(String id, String namespace, long creationTimestamp, String location,
+                String organizationDescription, String businessName) {
+            super(id, namespace, creationTimestamp, location);
+            mOrganizationDescription = organizationDescription;
+            mBusinessName = businessName;
+        }
+
+        public String getOrganizationDescription() {
+            return mOrganizationDescription;
+        }
+
+        public String getBusinessName() {
+            return mBusinessName;
+        }
+    }
+
+    @Test
+    public void testGenericDocumentConversion_AnnotatedInterface() throws Exception {
+        // Create Place document
+        Place place = Place.createPlace("id", "namespace", 1000, "loc");
+
+        // Test the conversion from Place to GenericDocument
+        GenericDocument genericDocument = GenericDocument.fromDocumentClass(place);
+        assertThat(genericDocument.getId()).isEqualTo("id");
+        assertThat(genericDocument.getNamespace()).isEqualTo("namespace");
+        assertThat(genericDocument.getCreationTimestampMillis()).isEqualTo(1000);
+        assertThat(genericDocument.getSchemaType()).isEqualTo("Place");
+        assertThat(genericDocument.getPropertyString("location")).isEqualTo("loc");
+
+        // Test the conversion from GenericDocument to Place
+        Place newPlace = genericDocument.toDocumentClass(Place.class);
+        assertThat(newPlace.getId()).isEqualTo("id");
+        assertThat(newPlace.getNamespace()).isEqualTo("namespace");
+        assertThat(newPlace.getCreationTimestamp()).isEqualTo(1000);
+        assertThat(newPlace.getLocation()).isEqualTo("loc");
+
+
+        // Create Business document
+        Business business = Business.createBusiness("id", "namespace", 2000, "business_loc",
+                "business_dec", "business_name");
+
+        // Test the conversion from Business to GenericDocument
+        genericDocument = GenericDocument.fromDocumentClass(business);
+        assertThat(genericDocument.getId()).isEqualTo("id");
+        assertThat(genericDocument.getNamespace()).isEqualTo("namespace");
+        assertThat(genericDocument.getCreationTimestampMillis()).isEqualTo(2000);
+        // The schema type of business has to be "BusinessImpl" because
+        // GenericDocument.fromDocumentClass is looking up factory classes by runtime types. It will
+        // start finding a factory class for "BusinessImpl" first. If the class is not found, it
+        // will continue to search the unique parent type (class and interface in Java). In this
+        // case, BusinessImpl has more than one parent, so BusinessImpl must also be @Document
+        // annotated. Otherwise, no factor class will be found.
+        assertThat(genericDocument.getSchemaType()).isEqualTo("BusinessImpl");
+        assertThat(genericDocument.getPropertyString("location")).isEqualTo("business_loc");
+        assertThat(genericDocument.getPropertyString("organizationDescription")).isEqualTo(
+                "business_dec");
+        assertThat(genericDocument.getPropertyString("businessName")).isEqualTo("business_name");
+
+
+        // Test the conversion from GenericDocument to Business
+        Business newBusiness = genericDocument.toDocumentClass(Business.class);
+        assertThat(newBusiness.getId()).isEqualTo("id");
+        assertThat(newBusiness.getNamespace()).isEqualTo("namespace");
+        assertThat(newBusiness.getCreationTimestamp()).isEqualTo(2000);
+        assertThat(newBusiness.getLocation()).isEqualTo("business_loc");
+        assertThat(newBusiness.getOrganizationDescription()).isEqualTo("business_dec");
+        assertThat(newBusiness.getBusinessName()).isEqualTo("business_name");
+    }
+
+    @Test
+    public void testPolymorphismForInterface() throws Exception {
+        assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+        mSession.setSchemaAsync(new SetSchemaRequest.Builder()
+                // Adding BusinessImpl should be enough to add all the dependency classes.
+                .addDocumentClasses(BusinessImpl.class)
+                // Add some other class
+                .addDocumentClasses(Gift.class)
+                .build()).get();
+
+        // Create documents
+        InterfaceRoot root = InterfaceRoot.create("id0", "namespace", 1000);
+        GenericDocument rootGeneric = GenericDocument.fromDocumentClass(root);
+        assertThat(rootGeneric.getId()).isEqualTo("id0");
+        assertThat(rootGeneric.getNamespace()).isEqualTo("namespace");
+        assertThat(rootGeneric.getCreationTimestampMillis()).isEqualTo(1000);
+        assertThat(rootGeneric.getSchemaType()).isEqualTo("InterfaceRoot");
+
+        Place place = Place.createPlace("id1", "namespace", 2000, "place_loc");
+        GenericDocument placeGeneric = GenericDocument.fromDocumentClass(place);
+        assertThat(placeGeneric.getId()).isEqualTo("id1");
+        assertThat(placeGeneric.getNamespace()).isEqualTo("namespace");
+        assertThat(placeGeneric.getCreationTimestampMillis()).isEqualTo(2000);
+        assertThat(placeGeneric.getSchemaType()).isEqualTo("Place");
+        assertThat(placeGeneric.getPropertyString("location")).isEqualTo("place_loc");
+
+        Organization organization = Organization.createOrganization("id2", "namespace", 3000,
+                "organization_dec");
+        GenericDocument organizationGeneric = GenericDocument.fromDocumentClass(organization);
+        assertThat(organizationGeneric.getId()).isEqualTo("id2");
+        assertThat(organizationGeneric.getNamespace()).isEqualTo("namespace");
+        assertThat(organizationGeneric.getCreationTimestampMillis()).isEqualTo(3000);
+        assertThat(organizationGeneric.getSchemaType()).isEqualTo("Organization");
+        assertThat(organizationGeneric.getPropertyString("organizationDescription")).isEqualTo(
+                "organization_dec");
+
+        Business business = Business.createBusiness("id3", "namespace", 4000, "business_loc",
+                "business_dec", "business_name");
+        GenericDocument businessGeneric = GenericDocument.fromDocumentClass(business);
+        assertThat(businessGeneric.getId()).isEqualTo("id3");
+        assertThat(businessGeneric.getNamespace()).isEqualTo("namespace");
+        assertThat(businessGeneric.getCreationTimestampMillis()).isEqualTo(4000);
+        // The type of business should be BusinessImpl because it's annotated with @Document.
+        assertThat(businessGeneric.getSchemaType()).isEqualTo("BusinessImpl");
+        assertThat(businessGeneric.getPropertyString("location")).isEqualTo(
+                "business_loc");
+        assertThat(businessGeneric.getPropertyString("organizationDescription")).isEqualTo(
+                "business_dec");
+
+        Gift gift = new Gift();
+        gift.mNamespace = "namespace";
+        gift.mId = "id4";
+        gift.mCreationTimestampMillis = 5000;
+        GenericDocument giftGeneric = GenericDocument.fromDocumentClass(gift);
+
+        checkIsBatchResultSuccess(mSession.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addDocuments(root, place, organization, business, gift)
+                        .build()));
+
+        // Query for all documents
+        SearchResults searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(rootGeneric, placeGeneric, organizationGeneric,
+                businessGeneric, giftGeneric);
+
+        // A query with a filter for the "InterfaceRoot" type should also include "Place",
+        // "Organization" and "Business".
+        searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterDocumentClasses(InterfaceRoot.class)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(rootGeneric, placeGeneric, organizationGeneric,
+                businessGeneric);
+
+        // A query with a filter for the "Place" type should also include "Business".
+        searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterDocumentClasses(Place.class)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(placeGeneric, businessGeneric);
+
+        // A query with a filter for the "Organization" type should also include "Business".
+        searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterDocumentClasses(Organization.class)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(organizationGeneric, businessGeneric);
+
+        // Query with a filter for the "Business" type.
+        searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterDocumentClasses(Business.class)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(businessGeneric);
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
index f040d94..f31e56b 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
@@ -265,6 +265,25 @@
     }
 
     @Test
+    public void testParentTypes() {
+        AppSearchSchema schema = new AppSearchSchema.Builder("EmailMessage")
+                .addParentType("Email")
+                .addParentType("Message")
+                .build();
+        assertThat(schema.getParentTypes()).containsExactly("Email", "Message");
+    }
+
+    @Test
+    public void testDuplicateParentTypes() {
+        AppSearchSchema schema = new AppSearchSchema.Builder("EmailMessage")
+                .addParentType("Email")
+                .addParentType("Message")
+                .addParentType("Email")
+                .build();
+        assertThat(schema.getParentTypes()).containsExactly("Email", "Message");
+    }
+
+    @Test
     public void testInvalidStringPropertyConfigsTokenizerNone() {
         // Everything should work fine with the defaults.
         final StringPropertyConfig.Builder builder =
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
index 01924df..be2b72c 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
@@ -741,6 +741,190 @@
     }
 
     @Test
+    public void testGetSchema_parentTypes() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder("Email").build();
+        AppSearchSchema messageSchema =
+                new AppSearchSchema.Builder("Message").build();
+        AppSearchSchema emailMessageSchema = new AppSearchSchema.Builder("EmailMessage")
+                .addProperty(
+                        new StringPropertyConfig.Builder("sender").setCardinality(
+                                PropertyConfig.CARDINALITY_REQUIRED).build())
+                .addProperty(
+                        new StringPropertyConfig.Builder("email").setCardinality(
+                                PropertyConfig.CARDINALITY_REQUIRED).build())
+                .addProperty(
+                        new StringPropertyConfig.Builder("content").setCardinality(
+                                PropertyConfig.CARDINALITY_REQUIRED).build())
+                .addParentType("Email")
+                .addParentType("Message")
+                .build();
+
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(emailMessageSchema)
+                .addSchemas(emailSchema)
+                .addSchemas(messageSchema)
+                .build();
+
+        mDb1.setSchemaAsync(request).get();
+
+        Set<AppSearchSchema> actual = mDb1.getSchemaAsync().get().getSchemas();
+        assertThat(actual).hasSize(3);
+        assertThat(actual).isEqualTo(request.getSchemas());
+    }
+
+    @Test
+    public void testGetSchema_parentTypes_notSupported() throws Exception {
+        assumeFalse(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder("Email").build();
+        AppSearchSchema messageSchema =
+                new AppSearchSchema.Builder("Message").build();
+        AppSearchSchema emailMessageSchema = new AppSearchSchema.Builder("EmailMessage")
+                .addParentType("Email")
+                .addParentType("Message")
+                .build();
+
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(emailMessageSchema)
+                .addSchemas(emailSchema)
+                .addSchemas(messageSchema)
+                .build();
+
+        UnsupportedOperationException e = assertThrows(UnsupportedOperationException.class, () ->
+                mDb1.setSchemaAsync(request).get());
+        assertThat(e).hasMessageThat().contains(Features.SCHEMA_ADD_PARENT_TYPE
+                + " is not available on this AppSearch implementation.");
+    }
+
+    @Test
+    public void testSetSchema_dataTypeIncompatibleWithParentTypes() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+        AppSearchSchema messageSchema =
+                new AppSearchSchema.Builder("Message")
+                        .addProperty(
+                                new LongPropertyConfig.Builder("sender").setCardinality(
+                                        PropertyConfig.CARDINALITY_REQUIRED).build())
+                        .build();
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder("Email")
+                        .addParentType("Message")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("sender").setCardinality(
+                                        PropertyConfig.CARDINALITY_REQUIRED).build())
+                        .build();
+
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(messageSchema)
+                .addSchemas(emailSchema)
+                .build();
+
+        ExecutionException executionException = assertThrows(ExecutionException.class,
+                () -> mDb1.setSchemaAsync(request).get());
+        assertThat(executionException).hasCauseThat().isInstanceOf(AppSearchException.class);
+        AppSearchException exception = (AppSearchException) executionException.getCause();
+        assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_ARGUMENT);
+        assertThat(exception).hasMessageThat().contains(
+                "Property sender from child type androidx.appsearch.test$/Email is not compatible"
+                        + " to the parent type androidx.appsearch.test$/Message.");
+    }
+
+    @Test
+    public void testSetSchema_documentTypeIncompatibleWithParentTypes() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+        AppSearchSchema personSchema = new AppSearchSchema.Builder("Person")
+                .build();
+        AppSearchSchema artistSchema = new AppSearchSchema.Builder("Artist")
+                .addParentType("Person")
+                .build();
+        AppSearchSchema messageSchema =
+                new AppSearchSchema.Builder("Message")
+                        .addProperty(
+                                new DocumentPropertyConfig.Builder("sender",
+                                        "Artist").setCardinality(
+                                        PropertyConfig.CARDINALITY_REQUIRED).build())
+                        .build();
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder("Email")
+                        .addParentType("Message")
+                        // "sender" is defined as an Artist in the parent type Message, which
+                        // requires "sender"'s type here to be a subtype of Artist. Thus, this is
+                        // incompatible because Person is not a subtype of Artist.
+                        .addProperty(
+                                new DocumentPropertyConfig.Builder("sender",
+                                        "Person").setCardinality(
+                                        PropertyConfig.CARDINALITY_REQUIRED).build())
+                        .build();
+
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(personSchema)
+                .addSchemas(artistSchema)
+                .addSchemas(messageSchema)
+                .addSchemas(emailSchema)
+                .build();
+
+        ExecutionException executionException = assertThrows(ExecutionException.class,
+                () -> mDb1.setSchemaAsync(request).get());
+        assertThat(executionException).hasCauseThat().isInstanceOf(AppSearchException.class);
+        AppSearchException exception = (AppSearchException) executionException.getCause();
+        assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_ARGUMENT);
+        assertThat(exception).hasMessageThat().contains(
+                "Property sender from child type androidx.appsearch.test$/Email is not compatible"
+                        + " to the parent type androidx.appsearch.test$/Message.");
+    }
+
+    @Test
+    public void testSetSchema_compatibleWithParentTypes() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+        AppSearchSchema personSchema = new AppSearchSchema.Builder("Person")
+                .build();
+        AppSearchSchema artistSchema = new AppSearchSchema.Builder("Artist")
+                .addParentType("Person")
+                .build();
+        AppSearchSchema messageSchema =
+                new AppSearchSchema.Builder("Message")
+                        .addProperty(
+                                new DocumentPropertyConfig.Builder("sender",
+                                        "Person").setCardinality(
+                                        PropertyConfig.CARDINALITY_REQUIRED).build())
+                        .addProperty(new StringPropertyConfig.Builder("note")
+                                .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                .build())
+                        .build();
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder("Email")
+                        .addParentType("Message")
+                        .addProperty(
+                                // Artist is a subtype of Person, so compatible
+                                new DocumentPropertyConfig.Builder("sender",
+                                        "Artist").setCardinality(
+                                        PropertyConfig.CARDINALITY_REQUIRED).build())
+                        .addProperty(new StringPropertyConfig.Builder("note")
+                                .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                // A different indexing or tokenizer type is ok.
+                                .setIndexingType(StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_VERBATIM)
+                                .build())
+                        .build();
+
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(personSchema)
+                .addSchemas(artistSchema)
+                .addSchemas(messageSchema)
+                .addSchemas(emailSchema)
+                .build();
+
+        mDb1.setSchemaAsync(request).get();
+
+        Set<AppSearchSchema> actual = mDb1.getSchemaAsync().get().getSchemas();
+        assertThat(actual).hasSize(4);
+        assertThat(actual).isEqualTo(request.getSchemas());
+    }
+
+    @Test
     public void testGetNamespaces() throws Exception {
         // Schema registration
         mDb1.setSchemaAsync(
@@ -2142,6 +2326,78 @@
     }
 
     @Test
+    public void testQuery_typeFilterWithPolymorphism() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+        // Schema registration
+        AppSearchSchema personSchema = new AppSearchSchema.Builder("Person")
+                .addProperty(new StringPropertyConfig.Builder("name")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+                ).build();
+        AppSearchSchema artistSchema = new AppSearchSchema.Builder("Artist")
+                .addParentType("Person")
+                .addProperty(new StringPropertyConfig.Builder("name")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+                ).build();
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(personSchema)
+                        .addSchemas(artistSchema)
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .build()).get();
+
+        // Index some documents
+        GenericDocument personDoc = new GenericDocument.Builder<>("namespace", "id1", "Person")
+                .setPropertyString("name", "Foo")
+                .build();
+        GenericDocument artistDoc = new GenericDocument.Builder<>("namespace", "id2", "Artist")
+                .setPropertyString("name", "Foo")
+                .build();
+        AppSearchEmail emailDoc =
+                new AppSearchEmail.Builder("namespace", "id3")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("Foo")
+                        .build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder().addGenericDocuments(personDoc, artistDoc,
+                        emailDoc).build()));
+
+        // Query for the documents
+        SearchResults searchResults = mDb1.search("Foo", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(3);
+        assertThat(documents).containsExactly(personDoc, artistDoc, emailDoc);
+
+        // Query with a filter for the "Person" type should also include the "Artist" type.
+        searchResults = mDb1.search("Foo", new SearchSpec.Builder()
+                .addFilterSchemas("Person")
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents).containsExactly(personDoc, artistDoc);
+
+        // Query with a filters for the "Artist" type should not include the "Person" type.
+        searchResults = mDb1.search("Foo", new SearchSpec.Builder()
+                .addFilterSchemas("Artist")
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(artistDoc);
+    }
+
+    @Test
     public void testQuery_packageFilter() throws Exception {
         // Schema registration
         mDb1.setSchemaAsync(
@@ -2391,6 +2647,92 @@
     }
 
     @Test
+    public void testQuery_projectionWithPolymorphism() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+        // Schema registration
+        AppSearchSchema personSchema = new AppSearchSchema.Builder("Person")
+                .addProperty(new StringPropertyConfig.Builder("name")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+                )
+                .addProperty(new StringPropertyConfig.Builder("emailAddress")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+                ).build();
+        AppSearchSchema artistSchema = new AppSearchSchema.Builder("Artist")
+                .addParentType("Person")
+                .addProperty(new StringPropertyConfig.Builder("name")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+                )
+                .addProperty(new StringPropertyConfig.Builder("emailAddress")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+                )
+                .addProperty(new StringPropertyConfig.Builder("company")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build()
+                ).build();
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(personSchema)
+                        .addSchemas(artistSchema)
+                        .build()).get();
+
+        // Index two documents
+        GenericDocument personDoc = new GenericDocument.Builder<>("namespace", "id1", "Person")
+                .setCreationTimestampMillis(1000)
+                .setPropertyString("name", "Foo Person")
+                .setPropertyString("emailAddress", "person@gmail.com")
+                .build();
+        GenericDocument artistDoc = new GenericDocument.Builder<>("namespace", "id2", "Artist")
+                .setCreationTimestampMillis(1000)
+                .setPropertyString("name", "Foo Artist")
+                .setPropertyString("emailAddress", "artist@gmail.com")
+                .setPropertyString("company", "Company")
+                .build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addGenericDocuments(personDoc, artistDoc).build()));
+
+        // Query with type property paths {"Person", ["name"]}, {"Artist", ["emailAddress"]}
+        // This will be expanded to paths {"Person", ["name"]}, {"Artist", ["name", "emailAddress"]}
+        // via polymorphism.
+        SearchResults searchResults = mDb1.search("Foo", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .addProjection("Person", ImmutableList.of("name"))
+                .addProjection("Artist", ImmutableList.of("emailAddress"))
+                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The person document should have been returned with only the "name" property. The artist
+        // document should have been returned with all of its properties.
+        GenericDocument expectedPerson =
+                new GenericDocument.Builder<>("namespace", "id1", "Person")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("name", "Foo Person")
+                        .build();
+        GenericDocument expectedArtist =
+                new GenericDocument.Builder<>("namespace", "id2", "Artist")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("name", "Foo Artist")
+                        .setPropertyString("emailAddress", "artist@gmail.com")
+                        .build();
+        assertThat(documents).containsExactly(expectedPerson, expectedArtist);
+    }
+
+    @Test
     public void testQuery_projectionEmpty() throws Exception {
         // Schema registration
         mDb1.setSchemaAsync(
@@ -2754,6 +3096,78 @@
     }
 
     @Test
+    public void testQuery_indexBasedOnParentTypePolymorphism() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+        // Schema registration
+        AppSearchSchema personSchema = new AppSearchSchema.Builder("Person")
+                .addProperty(new StringPropertyConfig.Builder("name")
+                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build())
+                .build();
+        AppSearchSchema artistSchema = new AppSearchSchema.Builder("Artist")
+                .addParentType("Person")
+                .addProperty(new StringPropertyConfig.Builder("name")
+                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build())
+                .addProperty(new StringPropertyConfig.Builder("company")
+                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .build())
+                .build();
+        AppSearchSchema messageSchema =
+                new AppSearchSchema.Builder("Message")
+                        .addProperty(
+                                new DocumentPropertyConfig.Builder("sender", "Person")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setShouldIndexNestedProperties(true)
+                                        .build())
+                        .build();
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(personSchema)
+                        .addSchemas(artistSchema)
+                        .addSchemas(messageSchema)
+                        .build()).get();
+
+        // Index some an artistDoc and a messageDoc
+        GenericDocument artistDoc = new GenericDocument.Builder<>("namespace", "id1", "Artist")
+                .setPropertyString("name", "Foo")
+                .setPropertyString("company", "Bar")
+                .build();
+        GenericDocument messageDoc = new GenericDocument.Builder<>("namespace", "id2", "Message")
+                // sender is defined as a Person, which accepts an Artist because Artist <: Person.
+                // However, indexing will be based on what's defined in Person, so the "company"
+                // property in artistDoc cannot be used to search this messageDoc.
+                .setPropertyDocument("sender", artistDoc)
+                .build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder().addGenericDocuments(artistDoc,
+                        messageDoc).build()));
+
+        // Query for the documents
+        SearchResults searchResults = mDb1.search("Foo", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents).containsExactly(artistDoc, messageDoc);
+
+        // The "company" property in artistDoc cannot be used to search messageDoc.
+        searchResults = mDb1.search("Bar", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(artistDoc);
+    }
+
+    @Test
     public void testSnippet() throws Exception {
         // Schema registration
         AppSearchSchema genericSchema = new AppSearchSchema.Builder("Generic")
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java
index 0033e71..b071c25 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java
@@ -915,5 +915,78 @@
         assertThat(schemas).contains(registry.getOrCreateFactory(Parent.class).getSchema());
     }
 
+    @Document
+    static class ArtType {
+        @Document.Id String mId;
+        @Document.Namespace String mNamespace;
+        @Document.StringProperty String mType;
+    }
+
+    @Document(name = "Artist", parent = {Person.class})
+    static class Artist extends Person {
+        @Document.StringProperty String mCompany;
+        @Document.DocumentProperty ArtType mArtType;
+    }
+
+    @Test
+    public void testSchemaPolymorphism() throws AppSearchException {
+        SetSchemaRequest request = new SetSchemaRequest.Builder().addDocumentClasses(Artist.class)
+                .setForceOverride(true).build();
+        DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
+
+        Set<AppSearchSchema> schemas = request.getSchemas();
+        // Artist's dependencies should be automatically added.
+        assertThat(schemas).containsExactly(
+                registry.getOrCreateFactory(Common.class).getSchema(),
+                registry.getOrCreateFactory(Person.class).getSchema(),
+                registry.getOrCreateFactory(ArtType.class).getSchema(),
+                registry.getOrCreateFactory(Artist.class).getSchema()
+        );
+    }
+
+    @Document
+    static class Thing {
+        @Document.Id String mId;
+        @Document.Namespace String mNamespace;
+        @Document.StringProperty String mHash;
+    }
+
+    @Document(name = "Email", parent = Thing.class)
+    static class Email extends Thing {
+        @Document.StringProperty String mSender;
+    }
+
+    @Document(name = "Message", parent = Thing.class)
+    static class Message extends Thing {
+        @Document.StringProperty String mContent;
+    }
+
+    // EmailMessage can choose any class to "extends" from, since Java's type relationship is
+    // independent on AppSearch's. In this case, EmailMessage extends Thing to avoid redefining
+    // mId, mNamespace and mHash, but it still needs to specify mSender and mContent coming from
+    // Email and Message.
+    @Document(name = "EmailMessage", parent = {Email.class, Message.class})
+    static class EmailMessage extends Thing {
+        @Document.StringProperty String mSender;
+        @Document.StringProperty String mContent;
+    }
+
+    @Test
+    public void testSchemaDiamondPolymorphism() throws AppSearchException {
+        SetSchemaRequest request = new SetSchemaRequest.Builder().addDocumentClasses(
+                        EmailMessage.class)
+                .setForceOverride(true).build();
+        DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
+
+        Set<AppSearchSchema> schemas = request.getSchemas();
+        // EmailMessage's dependencies should be automatically added.
+        assertThat(schemas).containsExactly(
+                registry.getOrCreateFactory(Thing.class).getSchema(),
+                registry.getOrCreateFactory(Email.class).getSchema(),
+                registry.getOrCreateFactory(Message.class).getSchema(),
+                registry.getOrCreateFactory(EmailMessage.class).getSchema()
+        );
+    }
+
 // @exportToFramework:endStrip()
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java
index 893a197..8066b39 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java
@@ -88,6 +88,22 @@
     String name() default "";
 
     /**
+     * The list of {@link Document} annotated classes that this type inherits from, in the context
+     * of AppSearch.
+     *
+     * <p>Please note that the type systems in AppSearch and Java are not necessarily equivalent.
+     * Specifically, if Foo and Bar are two classes, Bar can be a parent type of Foo in
+     * AppSearch, but the Foo class does not have to extend the Bar class in Java. The converse
+     * holds as well. However, the most common use case is to align the two type systems for
+     * single parent pattern, given that if Foo extends Bar in Java, Bar's properties will
+     * automatically be copied into Foo so that it is not necessary to redefine every property in
+     * Foo.
+     *
+     * @see AppSearchSchema.Builder#addParentType(String)
+     */
+    Class<?>[] parent() default {};
+
+    /**
      * Marks a member field of a document as the document's unique identifier (ID).
      *
      * <p>Indexing a document with a particular ID replaces any existing documents with the same
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
index f0d6df8..50d91b7 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
@@ -24,6 +24,7 @@
 import androidx.annotation.RequiresFeature;
 import androidx.annotation.RestrictTo;
 import androidx.appsearch.annotation.CanIgnoreReturnValue;
+import androidx.appsearch.exceptions.AppSearchException;
 import androidx.appsearch.exceptions.IllegalSchemaException;
 import androidx.appsearch.util.BundleUtil;
 import androidx.appsearch.util.IndentingStringBuilder;
@@ -51,6 +52,7 @@
 public final class AppSearchSchema {
     private static final String SCHEMA_TYPE_FIELD = "schemaType";
     private static final String PROPERTIES_FIELD = "properties";
+    private static final String PARENT_TYPES_FIELD = "parentTypes";
 
     private final Bundle mBundle;
 
@@ -139,6 +141,18 @@
         return ret;
     }
 
+    /**
+     * Returns the list of parent types of this schema for polymorphism.
+     */
+    @NonNull
+    public List<String> getParentTypes() {
+        List<String> parentTypes = mBundle.getStringArrayList(AppSearchSchema.PARENT_TYPES_FIELD);
+        if (parentTypes == null) {
+            return Collections.emptyList();
+        }
+        return Collections.unmodifiableList(parentTypes);
+    }
+
     @Override
     public boolean equals(@Nullable Object other) {
         if (this == other) {
@@ -151,18 +165,22 @@
         if (!getSchemaType().equals(otherSchema.getSchemaType())) {
             return false;
         }
+        if (!getParentTypes().equals(otherSchema.getParentTypes())) {
+            return false;
+        }
         return getProperties().equals(otherSchema.getProperties());
     }
 
     @Override
     public int hashCode() {
-        return ObjectsCompat.hash(getSchemaType(), getProperties());
+        return ObjectsCompat.hash(getSchemaType(), getProperties(), getParentTypes());
     }
 
     /** Builder for {@link AppSearchSchema objects}. */
     public static final class Builder {
         private final String mSchemaType;
         private ArrayList<Bundle> mPropertyBundles = new ArrayList<>();
+        private ArraySet<String> mParentTypes = new ArraySet<>();
         private final Set<String> mPropertyNames = new ArraySet<>();
         private boolean mBuilt = false;
 
@@ -186,12 +204,88 @@
             return this;
         }
 
+        /**
+         * Adds a parent type to the given type for polymorphism, so that the given type will be
+         * considered as a subtype of {@code parentSchemaType}.
+         *
+         * <p>Subtype relations are automatically considered transitive, so callers are only
+         * required to provide direct parents. Specifically, if T1 &lt;: T2 and T2 &lt;: T3 are
+         * known, then T1 &lt;: T3 will be inferred automatically, where &lt;: is the subtype
+         * symbol.
+         *
+         * <p>Polymorphism is currently supported in the following ways:
+         * <ul>
+         *     <li>Search filters on a parent type will automatically be extended to the child
+         *     types as well. For example, if Artist &lt;: Person, then a search with a filter on
+         *     type Person (by calling {@link SearchSpec.Builder#addFilterSchemas}) will also
+         *     include documents of type Artist in the search result.
+         *     <li>In the projection API, the property paths to project specified for a
+         *     parent type will automatically be extended to the child types as well. If both a
+         *     parent type and one of its child type are specified in the projection API, the
+         *     parent type's paths will be merged into the child's. For more details on
+         *     projection, see {@link SearchSpec.Builder#addProjection}.
+         *     <li>A document property defined as type U is allowed to be set with a document of
+         *     type T, as long as T &lt;: U, but note that index will only be based on the defined
+         *     type, which is U. For example, consider a document of type "Company" with a
+         *     repeated "employees" field of type "Person". We can add employees of either
+         *     type "Person" or type "Artist" or both to this property, as long as "Artist" is a
+         *     subtype of "Person". However, the index of the "employees" property will be based
+         *     on what's defined in "Person", even for an added document of type "Artist".
+         * </ul>
+         *
+         * <p>Subtypes must meet the following requirements. A violation of the requirements will
+         * cause {@link AppSearchSession#setSchemaAsync} to throw an {@link AppSearchException}
+         * with the result code of {@link AppSearchResult#RESULT_INVALID_ARGUMENT}. Consider a
+         * type Artist and a type Person, and Artist claims to be a subtype of Person, then:
+         * <ul>
+         *     <li>Every property in Person must have a corresponding property in Artist with the
+         *     same name.
+         *     <li>Every non-document property in Person must have the same type as the type of
+         *     the corresponding property in Artist. For example, if "age" is an integer property
+         *     in Person, then "age" must also be an integer property in Artist, instead of a
+         *     string.
+         *     <li>The schema type of every document property in Artist must be a subtype of the
+         *     schema type of the corresponding document property in Person, if such a property
+         *     exists in Person. For example, if "awards" is a document property of type Award in
+         *     Person, then the type of the "awards" property in Artist must be a subtype of
+         *     Award, say ArtAward. Note that every type is a subtype of itself.
+         *     <li>Every property in Artist must have a cardinality stricter than or equal to the
+         *     cardinality of the corresponding property in Person, if such a property exists in
+         *     Person. For example, if "awards" is a property in Person of cardinality OPTIONAL,
+         *     then the cardinality of the "awards" property in Artist can only be REQUIRED or
+         *     OPTIONAL. Rule: REQUIRED &lt; OPTIONAL &lt; REPEATED.
+         *     <li>There are no other enforcements on the corresponding properties in Artist,
+         *     such as index type, tokenizer type, etc. These settings can be safely overridden.
+         * </ul>
+         *
+         * <p>A type can be defined to have multiple parents, but it must be compatible with each
+         * of its parents based on the above rules. For example, if LocalBusiness is defined as a
+         * subtype of both Place and Organization, then the compatibility of LocalBusiness with
+         * Place and the compatibility of LocalBusiness with Organization will both be checked.
+         */
+        // TODO(b/280698873): Disallow polymorphism in AppSearch framework for Android T.
+        @CanIgnoreReturnValue
+        @NonNull
+        // @exportToFramework:startStrip()
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SCHEMA_ADD_PARENT_TYPE)
+        // @exportToFramework:endStrip()
+        public AppSearchSchema.Builder addParentType(@NonNull String parentSchemaType) {
+            Preconditions.checkNotNull(parentSchemaType);
+            resetIfBuilt();
+            mParentTypes.add(parentSchemaType);
+            return this;
+        }
+
         /** Constructs a new {@link AppSearchSchema} from the contents of this builder. */
         @NonNull
         public AppSearchSchema build() {
             Bundle bundle = new Bundle();
             bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mSchemaType);
             bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mPropertyBundles);
+            bundle.putStringArrayList(AppSearchSchema.PARENT_TYPES_FIELD,
+                    new ArrayList<>(mParentTypes));
             mBuilt = true;
             return new AppSearchSchema(bundle);
         }
@@ -199,6 +293,7 @@
         private void resetIfBuilt() {
             if (mBuilt) {
                 mPropertyBundles = new ArrayList<>(mPropertyBundles);
+                mParentTypes = new ArraySet<>(mParentTypes);
                 mBuilt = false;
             }
         }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactory.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactory.java
index fd5987e..4b6c719 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactory.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactory.java
@@ -41,11 +41,11 @@
     AppSearchSchema getSchema() throws AppSearchException;
 
     /**
-     * Returns dependent document classes used in this document class. This is useful as we can set
-     * dependent schemas without requiring clients to explicitly set all dependent schemas.
+     * Returns document classes that this document class depends on. This is useful so clients
+     * are not required to explicitly set all dependencies.
      */
     @NonNull
-    List<Class<?>> getNestedDocumentClasses() throws AppSearchException;
+    List<Class<?>> getDependencyDocumentClasses() throws AppSearchException;
 
     /**
      * Converts an instance of the class annotated with
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactoryRegistry.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactoryRegistry.java
index a057998..59f84f5 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactoryRegistry.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactoryRegistry.java
@@ -128,12 +128,36 @@
         try {
             factoryClass = Class.forName(factoryClassName);
         } catch (ClassNotFoundException e) {
-            throw new AppSearchException(
-                    AppSearchResult.RESULT_INTERNAL_ERROR,
-                    "Failed to find document class converter \"" + factoryClassName
-                            + "\". Perhaps the annotation processor was not run or the class was "
-                            + "proguarded out?",
-                    e);
+            // If the current class or interface has only one parent interface/class, then try to
+            // look at the unique parent.
+            Class<?> superClass = documentClass.getSuperclass();
+            Class<?>[] superInterfaces = documentClass.getInterfaces();
+            if (superClass == Object.class) {
+                superClass = null;
+            }
+            int numParent = superInterfaces.length;
+            if (superClass != null) {
+                numParent += 1;
+            }
+
+            if (numParent == 1) {
+                if (superClass != null) {
+                    return loadFactoryByReflection(superClass);
+                } else {
+                    return loadFactoryByReflection(superInterfaces[0]);
+                }
+            }
+
+            String errorMessage = "Failed to find document class converter \"" + factoryClassName
+                    + "\". Perhaps the annotation processor was not run or the class was "
+                    + "proguarded out?";
+            if (numParent > 1) {
+                errorMessage += " Or, this class may not have been annotated with @Document, and "
+                        + "there is an ambiguity to determine a unique @Document annotated parent "
+                        + "class/interface.";
+            }
+
+            throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, errorMessage, e);
         }
         Object instance;
         try {
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
index a555b2d..6309e87 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
@@ -152,6 +152,12 @@
     String SET_SCHEMA_CIRCULAR_REFERENCES = "SET_SCHEMA_CIRCULAR_REFERENCES";
 
     /**
+     * Feature for {@link #isFeatureSupported(String)}. This feature covers
+     * {@link AppSearchSchema.Builder#addParentType}.
+     */
+    String SCHEMA_ADD_PARENT_TYPE = "SCHEMA_ADD_PARENT_TYPE";
+
+    /**
      * Returns whether a feature is supported at run-time. Feature support depends on the
      * feature in question, the AppSearch backend being used and the Android version of the
      * device.
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SetSchemaRequest.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SetSchemaRequest.java
index e85171a..cccee87 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SetSchemaRequest.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SetSchemaRequest.java
@@ -389,7 +389,7 @@
             for (Class<?> documentClass : documentClasses) {
                 DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
                 schemas.add(factory.getSchema());
-                addDocumentClasses(factory.getNestedDocumentClasses());
+                addDocumentClasses(factory.getDependencyDocumentClasses());
             }
             return addSchemas(schemas);
         }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AppSearchCompiler.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AppSearchCompiler.java
index b59bea9..f15e793 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AppSearchCompiler.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AppSearchCompiler.java
@@ -109,9 +109,11 @@
 
         private void processDocument(@NonNull TypeElement element)
                 throws ProcessingException, MissingTypeException {
-            if (element.getKind() != ElementKind.CLASS) {
+            if (element.getKind() != ElementKind.CLASS
+                    && element.getKind() != ElementKind.INTERFACE) {
                 throw new ProcessingException(
-                        "@Document annotation on something other than a class", element);
+                        "@Document annotation on something other than a class or an interface",
+                        element);
             }
 
             DocumentModel model;
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
index 9e0cd1bc..70780ce 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
@@ -47,6 +47,7 @@
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
 import javax.lang.model.util.Types;
 
 /**
@@ -66,35 +67,41 @@
     /** Determines how the annotation processor has decided to write the value of a field. */
     enum WriteKind {FIELD, SETTER, CREATION_METHOD}
 
+    private static final String CLASS_SUFFIX = ".class";
+
     private final IntrospectionHelper mHelper;
     private final TypeElement mClass;
     private final Types mTypeUtil;
+    private final Elements mElementUtil;
     // The name of the original class annotated with @Document
     private final String mQualifiedDocumentClassName;
     private String mSchemaName;
+    private Set<TypeElement> mParentTypes = new LinkedHashSet<>();
     // Warning: if you change this to a HashSet, we may choose different getters or setters from
     // run to run, causing the generated code to bounce.
     private final Set<ExecutableElement> mAllMethods = new LinkedHashSet<>();
     private final boolean mIsAutoValueDocument;
-    // Key: Name of the field which is accessed through the getter method.
+    // Key: Name of the element which is accessed through the getter method.
     // Value: ExecutableElement of the getter method.
     private final Map<String, ExecutableElement> mGetterMethods = new HashMap<>();
-    // Key: Name of the field whose value is set through the setter method.
+    // Key: Name of the element whose value is set through the setter method.
     // Value: ExecutableElement of the setter method.
     private final Map<String, ExecutableElement> mSetterMethods = new HashMap<>();
-    // Warning: if you change this to a HashMap, we may assign fields in a different order from run
-    // to run, causing the generated code to bounce.
-    // Keeps tracks of all AppSearch fields so we can find creation and access methods for them all
-    private final Map<String, VariableElement> mAllAppSearchFields = new LinkedHashMap<>();
-    // Warning: if you change this to a HashMap, we may assign fields in a different order from run
-    // to run, causing the generated code to bounce.
-    // Keeps track of property fields so we don't allow multiple annotated fields of the same name
-    private final Map<String, VariableElement> mPropertyFields = new LinkedHashMap<>();
+    // Warning: if you change this to a HashMap, we may assign elements in a different order from
+    // run to run, causing the generated code to bounce.
+    // Keeps tracks of all AppSearch elements so we can find creation and access methods for them
+    // all
+    private final Map<String, Element> mAllAppSearchElements = new LinkedHashMap<>();
+    // Warning: if you change this to a HashMap, we may assign elements in a different order from
+    // run to run, causing the generated code to bounce.
+    // Keeps track of property elements so we don't allow multiple annotated elements of the same
+    // name
+    private final Map<String, Element> mPropertyElements = new LinkedHashMap<>();
     private final Map<SpecialField, String> mSpecialFieldNames = new EnumMap<>(SpecialField.class);
-    private final Map<VariableElement, ReadKind> mReadKinds = new HashMap<>();
-    private final Map<VariableElement, WriteKind> mWriteKinds = new HashMap<>();
-    // Contains the reason why that field couldn't be written either by field or by setter.
-    private final Map<VariableElement, ProcessingException> mWriteWhyCreationMethod =
+    private final Map<Element, ReadKind> mReadKinds = new HashMap<>();
+    private final Map<Element, WriteKind> mWriteKinds = new HashMap<>();
+    // Contains the reason why that element couldn't be written either by field or by setter.
+    private final Map<Element, ProcessingException> mWriteWhyCreationMethod =
             new HashMap<>();
     private ExecutableElement mChosenCreationMethod = null;
     private List<String> mChosenCreationMethodParams = null;
@@ -111,6 +118,7 @@
         mHelper = new IntrospectionHelper(env);
         mClass = clazz;
         mTypeUtil = env.getTypeUtils();
+        mElementUtil = env.getElementUtils();
 
         if (generatedAutoValueElement != null) {
             mIsAutoValueDocument = true;
@@ -167,6 +175,11 @@
         if (superClass.getKind().equals(TypeKind.DECLARED)) {
             addAllMethods((TypeElement) mTypeUtil.asElement(superClass), allMethods);
         }
+        for (TypeMirror implementedInterface : typeElement.getInterfaces()) {
+            if (implementedInterface.getKind().equals(TypeKind.DECLARED)) {
+                addAllMethods((TypeElement) mTypeUtil.asElement(implementedInterface), allMethods);
+            }
+        }
     }
 
     /**
@@ -213,14 +226,22 @@
         return mSchemaName;
     }
 
+    /**
+     * Returns the set of parent classes specified in @Document via the "parent" parameter.
+     */
     @NonNull
-    public Map<String, VariableElement> getAllFields() {
-        return Collections.unmodifiableMap(mAllAppSearchFields);
+    public Set<TypeElement> getParentTypes() {
+        return mParentTypes;
     }
 
     @NonNull
-    public Map<String, VariableElement> getPropertyFields() {
-        return Collections.unmodifiableMap(mPropertyFields);
+    public Map<String, Element> getAllElements() {
+        return Collections.unmodifiableMap(mAllAppSearchElements);
+    }
+
+    @NonNull
+    public Map<String, Element> getPropertyElements() {
+        return Collections.unmodifiableMap(mPropertyElements);
     }
 
     @Nullable
@@ -229,25 +250,25 @@
     }
 
     @Nullable
-    public ReadKind getFieldReadKind(String fieldName) {
-        VariableElement element = mAllAppSearchFields.get(fieldName);
+    public ReadKind getElementReadKind(String elementName) {
+        Element element = mAllAppSearchElements.get(elementName);
         return mReadKinds.get(element);
     }
 
     @Nullable
-    public WriteKind getFieldWriteKind(String fieldName) {
-        VariableElement element = mAllAppSearchFields.get(fieldName);
+    public WriteKind getElementWriteKind(String elementName) {
+        Element element = mAllAppSearchElements.get(elementName);
         return mWriteKinds.get(element);
     }
 
     @Nullable
-    public ExecutableElement getGetterForField(String fieldName) {
-        return mGetterMethods.get(fieldName);
+    public ExecutableElement getGetterForElement(String elementName) {
+        return mGetterMethods.get(elementName);
     }
 
     @Nullable
-    public ExecutableElement getSetterForField(String fieldName) {
-        return mSetterMethods.get(fieldName);
+    public ExecutableElement getSetterForElement(String elementName) {
+        return mSetterMethods.get(elementName);
     }
 
     /**
@@ -257,12 +278,12 @@
      * specifies a different 'name' parameter in the annotation.
      */
     @NonNull
-    public String getPropertyName(@NonNull VariableElement property) throws ProcessingException {
+    public String getPropertyName(@NonNull Element property) throws ProcessingException {
         AnnotationMirror annotation = getPropertyAnnotation(property);
         Map<String, Object> params = mHelper.getAnnotationParams(annotation);
         String propertyName = params.get("name").toString();
         if (propertyName.isEmpty()) {
-            propertyName = getNormalizedFieldName(property.getSimpleName().toString());
+            propertyName = getNormalizedElementName(property);
         }
         return propertyName;
     }
@@ -278,7 +299,7 @@
             throws ProcessingException {
         Objects.requireNonNull(element);
         if (mIsAutoValueDocument) {
-            element = getGetterForField(element.getSimpleName().toString());
+            element = getGetterForElement(element.getSimpleName().toString());
         }
         Set<String> propertyClassPaths = new HashSet<>();
         for (PropertyClass propertyClass : PropertyClass.values()) {
@@ -325,7 +346,7 @@
         if (mSpecialFieldNames.containsValue(fieldName)) {
             throw new ProcessingException(
                     "Non-annotated field overriding special annotated fields named: "
-                            + fieldName, mAllAppSearchFields.get(fieldName));
+                            + fieldName, mAllAppSearchElements.get(fieldName));
         }
 
         // no annotation mirrors -> non-indexable field
@@ -334,17 +355,14 @@
             if (!annotationFq.startsWith(DOCUMENT_ANNOTATION_CLASS)) {
                 continue;
             }
-            VariableElement child;
+            Element child;
             if (mIsAutoValueDocument) {
                 child = findFieldForFunctionWithSameName(classElements, childElement);
             } else {
-                if (childElement.getKind() == ElementKind.METHOD) {
-                    throw new ProcessingException("AppSearch annotation is not applicable to "
-                            + "methods for Non-AutoValue class", childElement);
-                } else if (childElement.getKind() == ElementKind.CLASS) {
+                if (childElement.getKind() == ElementKind.CLASS) {
                     continue;
                 } else {
-                    child = (VariableElement) childElement;
+                    child = childElement;
                 }
             }
 
@@ -390,17 +408,32 @@
                 default:
                     PropertyClass propertyClass = getPropertyClass(annotationFq);
                     if (propertyClass != null) {
+                        // A property must either:
+                        //   1. be unique
+                        //   2. override a property from the Java parent while maintaining the same
+                        //      AppSearch property name
                         checkFieldTypeForPropertyAnnotation(child, propertyClass);
-                        if (mPropertyFields.containsKey(fieldName)) {
-                            throw new ProcessingException(
-                                    "Class hierarchy contains multiple annotated fields named: "
-                                            + fieldName, child);
+                        // It's assumed that parent types, in the context of Java's type system,
+                        // are always visited before child types, so existingProperty must come
+                        // from the parent type. To make this assumption valid, the result
+                        // returned by generateClassHierarchy must put parent types before child
+                        // types.
+                        Element existingProperty = mPropertyElements.get(fieldName);
+                        if (existingProperty != null) {
+                            if (!mTypeUtil.isSameType(existingProperty.asType(), child.asType())) {
+                                throw new ProcessingException(
+                                        "Cannot override a property with a different type", child);
+                            }
+                            if (!getPropertyName(existingProperty).equals(getPropertyName(child))) {
+                                throw new ProcessingException(
+                                        "Cannot override a property with a different name", child);
+                            }
                         }
-                        mPropertyFields.put(fieldName, child);
+                        mPropertyElements.put(fieldName, child);
                     }
             }
 
-            mAllAppSearchFields.put(fieldName, child);
+            mAllAppSearchElements.put(fieldName, child);
         }
     }
 
@@ -411,6 +444,25 @@
      * @param element the class to scan
      */
     private void scanFields(@NonNull TypeElement element) throws ProcessingException {
+        AnnotationMirror documentAnnotation = getDocumentAnnotation(element);
+        if (documentAnnotation != null) {
+            Map<String, Object> params = mHelper.getAnnotationParams(documentAnnotation);
+            Object parents = params.get("parent");
+            if (parents instanceof List) {
+                for (Object parent : (List<?>) parents) {
+                    String parentClassName = parent.toString();
+                    parentClassName = parentClassName.substring(0,
+                            parentClassName.length() - CLASS_SUFFIX.length());
+                    mParentTypes.add(mElementUtil.getTypeElement(parentClassName));
+                }
+            }
+            if (!mParentTypes.isEmpty() && params.get("name").toString().isEmpty()) {
+                throw new ProcessingException(
+                        "All @Document classes with a parent must explicitly provide a name",
+                        mClass);
+            }
+        }
+
         List<TypeElement> hierarchy = generateClassHierarchy(element, mIsAutoValueDocument);
 
         for (TypeElement clazz : hierarchy) {
@@ -444,17 +496,17 @@
 
         mSchemaName = computeSchemaName(hierarchy);
 
-        for (VariableElement appSearchField : mAllAppSearchFields.values()) {
+        for (Element appSearchField : mAllAppSearchElements.values()) {
             chooseAccessKinds(appSearchField);
         }
     }
 
     @NonNull
-    private VariableElement findFieldForFunctionWithSameName(
+    private Element findFieldForFunctionWithSameName(
             @NonNull List<? extends Element> elements,
             @NonNull Element functionElement) throws ProcessingException {
         String fieldName = functionElement.getSimpleName().toString();
-        for (VariableElement field : ElementFilter.fieldsIn(elements)) {
+        for (Element field : ElementFilter.fieldsIn(elements)) {
             if (fieldName.equals(field.getSimpleName().toString())) {
                 return field;
             }
@@ -470,7 +522,7 @@
      *
      * @throws ProcessingException if data type doesn't match property annotation's requirement.
      */
-    void checkFieldTypeForPropertyAnnotation(@NonNull VariableElement property,
+    void checkFieldTypeForPropertyAnnotation(@NonNull Element property,
             PropertyClass propertyClass) throws ProcessingException {
         switch (propertyClass) {
             case BOOLEAN_PROPERTY_CLASS:
@@ -543,12 +595,11 @@
      *
      * @throws ProcessingException if no access type is possible for the given field
      */
-    private void chooseAccessKinds(@NonNull VariableElement field)
+    private void chooseAccessKinds(@NonNull Element field)
             throws ProcessingException {
         // Choose get access
-        String fieldName = field.getSimpleName().toString();
         Set<Modifier> modifiers = field.getModifiers();
-        if (modifiers.contains(Modifier.PRIVATE)) {
+        if (modifiers.contains(Modifier.PRIVATE) || field.getKind() == ElementKind.METHOD) {
             findGetter(field);
             mReadKinds.put(field, ReadKind.GETTER);
         } else {
@@ -557,12 +608,12 @@
 
         // Choose set access
         if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.FINAL)
-                || modifiers.contains(Modifier.STATIC)) {
+                || modifiers.contains(Modifier.STATIC) || field.getKind() == ElementKind.METHOD) {
             // Try to find a setter. If we can't find one, mark the WriteKind as {@code
             // CREATION_METHOD}. We don't know if this is true yet, the creation methods will be
             // inspected in a subsequent pass.
             try {
-                findSetter(fieldName);
+                findSetter(field);
                 mWriteKinds.put(field, WriteKind.SETTER);
             } catch (ProcessingException e) {
                 // We'll look for a creation method, so we may still be able to set this field,
@@ -581,8 +632,8 @@
         // Maps field name to Element.
         // If this is changed to a HashSet, we might report errors to the developer in a different
         // order about why a field was written via creation method.
-        Map<String, VariableElement> creationMethodWrittenFields = new LinkedHashMap<>();
-        for (Map.Entry<VariableElement, WriteKind> it : mWriteKinds.entrySet()) {
+        Map<String, Element> creationMethodWrittenFields = new LinkedHashMap<>();
+        for (Map.Entry<Element, WriteKind> it : mWriteKinds.entrySet()) {
             if (it.getValue() == WriteKind.CREATION_METHOD) {
                 String name = it.getKey().getSimpleName().toString();
                 creationMethodWrittenFields.put(name, it.getKey());
@@ -591,8 +642,9 @@
 
         // Maps normalized field name to real field name.
         Map<String, String> normalizedToRawFieldName = new HashMap<>();
-        for (String fieldName : mAllAppSearchFields.keySet()) {
-            normalizedToRawFieldName.put(getNormalizedFieldName(fieldName), fieldName);
+        for (Element field : mAllAppSearchElements.values()) {
+            normalizedToRawFieldName.put(getNormalizedElementName(field),
+                    field.getSimpleName().toString());
         }
 
         Map<ExecutableElement, String> whyNotCreationMethod = new HashMap<>();
@@ -629,7 +681,7 @@
 
             // If the field is set in the constructor, choose creation method for the write kind
             for (String param : creationMethodParamFields) {
-                for (VariableElement appSearchField : mAllAppSearchFields.values()) {
+                for (Element appSearchField : mAllAppSearchElements.values()) {
                     if (appSearchField.getSimpleName().toString().equals(param)) {
                         mWriteKinds.put(appSearchField, WriteKind.CREATION_METHOD);
                         break;
@@ -652,7 +704,7 @@
                         mClass);
 
         // Inform the developer why we started looking for creation methods in the first place.
-        for (VariableElement field : creationMethodWrittenFields.values()) {
+        for (Element field : creationMethodWrittenFields.values()) {
             ProcessingException warning = mWriteWhyCreationMethod.get(field);
             if (warning != null) {
                 e.addWarning(warning);
@@ -670,27 +722,37 @@
         throw e;
     }
 
-    /** Finds getter function for a private field. */
-    private void findGetter(@NonNull VariableElement field) throws ProcessingException {
-        String fieldName = field.getSimpleName().toString();
-        ProcessingException e = new ProcessingException(
-                "Field cannot be read: it is private and we failed to find a suitable getter "
-                        + "for field \"" + fieldName + "\"",
-                mAllAppSearchFields.get(fieldName));
+    /**
+     * Finds getter function for a private field, or for a property defined by a annotated getter
+     * method, in which case the annotated element itself should be the getter unless it's
+     * private or it takes parameters.
+     */
+    private void findGetter(@NonNull Element element) throws ProcessingException {
+        String elementName = element.getSimpleName().toString();
+        ProcessingException e;
+        if (element.getKind() == ElementKind.METHOD) {
+            e = new ProcessingException(
+                    "Failed to find a suitable getter for element \"" + elementName + "\"",
+                    mAllAppSearchElements.get(elementName));
+        } else {
+            e = new ProcessingException(
+                    "Field cannot be read: it is private and we failed to find a suitable getter "
+                            + "for field \"" + elementName + "\"",
+                    mAllAppSearchElements.get(elementName));
+        }
 
         for (ExecutableElement method : mAllMethods) {
             String methodName = method.getSimpleName().toString();
-            String normalizedFieldName = getNormalizedFieldName(fieldName);
-            // normalizedFieldName with first letter capitalized, to be paired with [is] or [get]
+            String normalizedElementName = getNormalizedElementName(element);
+            // normalizedElementName with first letter capitalized, to be paired with [is] or [get]
             // prefix
-            String methodNameSuffix = normalizedFieldName.substring(0, 1).toUpperCase()
-                    + normalizedFieldName.substring(1);
+            String methodNameSuffix = normalizedElementName.substring(0, 1).toUpperCase()
+                    + normalizedElementName.substring(1);
 
-            if (methodName.equals(normalizedFieldName)
+            if (methodName.equals(normalizedElementName)
                     || methodName.equals("get" + methodNameSuffix)
                     || (
-                    mHelper.isFieldOfExactType(
-                            field, mHelper.mBooleanBoxType, mHelper.mBooleanPrimitiveType)
+                    mHelper.isFieldOfBooleanType(element)
                             && methodName.equals("is" + methodNameSuffix))
             ) {
                 if (method.getModifiers().contains(Modifier.PRIVATE)) {
@@ -704,7 +766,7 @@
                     continue;
                 }
                 // Found one!
-                mGetterMethods.put(fieldName, method);
+                mGetterMethods.put(elementName, method);
                 return;
             }
         }
@@ -713,24 +775,32 @@
         throw e;
     }
 
-    /** Finds setter function for a private field. */
-    private void findSetter(@NonNull String fieldName) throws ProcessingException {
+    /**
+     * Finds setter function for a private field, or for a property defined by a annotated getter
+     * method.
+     */
+    private void findSetter(@NonNull Element element) throws ProcessingException {
+        String elementName = element.getSimpleName().toString();
         // We can't report setter failure until we've searched the creation methods, so this
         // message is anticipatory and should be buffered by the caller.
-        ProcessingException e = new ProcessingException(
-                "Field cannot be written directly or via setter because it is private, final, or "
-                        + "static, and we failed to find a suitable setter for field \""
-                        + fieldName
-                        + "\". Trying to find a suitable creation method.",
-                mAllAppSearchFields.get(fieldName));
+        String error;
+        if (element.getKind() == ElementKind.METHOD) {
+            error = "Element cannot be written directly because it is an annotated getter";
+        } else {
+            error = "Field cannot be written directly because it is private, final, or static";
+        }
+        error += ", and we failed to find a suitable setter for \"" + elementName + "\". "
+                + "Trying to find a suitable creation method.";
+        ProcessingException e = new ProcessingException(error,
+                mAllAppSearchElements.get(elementName));
 
         for (ExecutableElement method : mAllMethods) {
             String methodName = method.getSimpleName().toString();
-            String normalizedFieldName = getNormalizedFieldName(fieldName);
-            if (methodName.equals(normalizedFieldName)
+            String normalizedElementName = getNormalizedElementName(element);
+            if (methodName.equals(normalizedElementName)
                     || methodName.equals("set"
-                    + normalizedFieldName.substring(0, 1).toUpperCase()
-                    + normalizedFieldName.substring(1))) {
+                    + normalizedElementName.substring(0, 1).toUpperCase()
+                    + normalizedElementName.substring(1))) {
                 if (method.getModifiers().contains(Modifier.PRIVATE)) {
                     e.addWarning(new ProcessingException(
                             "Setter cannot be used: private visibility", method));
@@ -744,7 +814,7 @@
                     continue;
                 }
                 // Found one!
-                mSetterMethods.put(fieldName, method);
+                mSetterMethods.put(elementName, method);
                 return;
             }
         }
@@ -754,10 +824,13 @@
     }
 
     /**
-     * Produces the canonical name of a field (which is used as the default property name as well as
-     * to find accessors) by removing prefixes and suffixes of common conventions.
+     * Produces the canonical name of a field element.
+     *
+     * @see #getNormalizedElementName(Element)
      */
-    private String getNormalizedFieldName(String fieldName) {
+    private String getNormalizedFieldElementName(Element fieldElement) {
+        String fieldName = fieldElement.getSimpleName().toString();
+
         if (fieldName.length() < 2) {
             return fieldName;
         }
@@ -787,6 +860,38 @@
     }
 
     /**
+     * Produces the canonical name of a method element.
+     *
+     * @see #getNormalizedElementName(Element)
+     */
+    private String getNormalizedMethodElementName(Element methodElement) {
+        String methodName = methodElement.getSimpleName().toString();
+
+        // If this property is defined by an annotated getter, then we can remove the prefix
+        // "get" or "is" if possible.
+        if (methodName.startsWith("get") && methodName.length() > 3) {
+            methodName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
+        } else if (mHelper.isFieldOfBooleanType(methodElement) && methodName.startsWith("is")
+                && methodName.length() > 2) {
+            // "is" is a valid getter prefix for boolean property.
+            methodName = methodName.substring(2, 3).toLowerCase() + methodName.substring(3);
+        }
+        // Return early because the rest normalization procedures do not apply to getters.
+        return methodName;
+    }
+
+    /**
+     * Produces the canonical name of a element (which is used as the default property name as
+     * well as to find accessors) by removing prefixes and suffixes of common conventions.
+     */
+    private String getNormalizedElementName(Element property) {
+        if (property.getKind() == ElementKind.METHOD) {
+            return getNormalizedMethodElementName(property);
+        }
+        return getNormalizedFieldElementName(property);
+    }
+
+    /**
      * Computes the schema name for a Document class given its hierarchy of parent @Document
      * classes.
      *
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
index 9bb751f..9ec0386 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
@@ -17,6 +17,7 @@
 package androidx.appsearch.compiler;
 
 import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentAnnotation;
+import static androidx.appsearch.compiler.IntrospectionHelper.getPropertyType;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -38,7 +39,6 @@
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.ArrayType;
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeKind;
@@ -85,7 +85,7 @@
         unpackSpecialFields(methodBuilder);
 
         // Unpack properties from the GenericDocument into the format desired by the document class
-        for (Map.Entry<String, VariableElement> entry : mModel.getPropertyFields().entrySet()) {
+        for (Map.Entry<String, Element> entry : mModel.getPropertyElements().entrySet()) {
             fieldFromGenericDoc(methodBuilder, entry.getKey(), entry.getValue());
         }
 
@@ -101,7 +101,7 @@
         }
 
         // Assign all fields which weren't set in the constructor
-        for (String field : mModel.getAllFields().keySet()) {
+        for (String field : mModel.getAllElements().keySet()) {
             CodeBlock fieldWrite = createAppSearchFieldWrite(field);
             if (fieldWrite != null) {
                 methodBuilder.addStatement(fieldWrite);
@@ -119,7 +119,7 @@
     private void fieldFromGenericDoc(
             @NonNull MethodSpec.Builder builder,
             @NonNull String fieldName,
-            @NonNull VariableElement property) throws ProcessingException {
+            @NonNull Element property) throws ProcessingException {
         // Scenario 1: field is assignable from List
         //   1a: ListForLoopAssign
         //       List contains boxed Long, Integer, Double, Float, Boolean or byte[]. We have to
@@ -200,27 +200,28 @@
             @NonNull MethodSpec.Builder method,
             @NonNull String fieldName,
             @NonNull String propertyName,
-            @NonNull VariableElement property) throws ProcessingException {
+            @NonNull Element property) throws ProcessingException {
         Types typeUtil = mEnv.getTypeUtils();
-        if (!typeUtil.isAssignable(mHelper.mListType, typeUtil.erasure(property.asType()))) {
+        TypeMirror propertyType = getPropertyType(property);
+        if (!typeUtil.isAssignable(mHelper.mListType, typeUtil.erasure(propertyType))) {
             return false;  // This is not a scenario 1 list
         }
 
-        List<? extends TypeMirror> genericTypes =
-                ((DeclaredType) property.asType()).getTypeArguments();
-        TypeMirror propertyType = genericTypes.get(0);
+        List<? extends TypeMirror> genericTypes = ((DeclaredType) propertyType).getTypeArguments();
+        TypeMirror componentType = genericTypes.get(0);
         ParameterizedTypeName listTypeName = ParameterizedTypeName.get(ClassName.get(List.class),
-                TypeName.get(propertyType));
+                TypeName.get(componentType));
 
         CodeBlock.Builder builder = CodeBlock.builder();
-        if (!tryListForLoopAssign(builder, fieldName, propertyName, propertyType, listTypeName)// 1a
+        if (!tryListForLoopAssign(builder, fieldName, propertyName,
+                componentType, listTypeName) // 1a
                 && !tryListCallArraysAsList(
-                builder, fieldName, propertyName, propertyType, listTypeName)          // 1b
+                builder, fieldName, propertyName, componentType, listTypeName)          // 1b
                 && !tryListForLoopCallFromGenericDocument(
-                builder, fieldName, propertyName, propertyType, listTypeName)) {       // 1c
+                builder, fieldName, propertyName, componentType, listTypeName)) {       // 1c
             // Scenario 1x
             throw new ProcessingException(
-                    "Unhandled in property type (1x): " + property.asType().toString(), property);
+                    "Unhandled in property type (1x): " + propertyType.toString(), property);
         }
 
         method.addCode(builder.build());
@@ -387,25 +388,26 @@
             @NonNull MethodSpec.Builder method,
             @NonNull String fieldName,
             @NonNull String propertyName,
-            @NonNull VariableElement property) throws ProcessingException {
+            @NonNull Element property) throws ProcessingException {
         Types typeUtil = mEnv.getTypeUtils();
-        if (property.asType().getKind() != TypeKind.ARRAY
+        TypeMirror propertyType = getPropertyType(property);
+        if (propertyType.getKind() != TypeKind.ARRAY
                 // Byte arrays have a native representation in Icing, so they are not considered a
                 // "repeated" type
-                || typeUtil.isSameType(property.asType(), mHelper.mBytePrimitiveArrayType)) {
+                || typeUtil.isSameType(propertyType, mHelper.mBytePrimitiveArrayType)) {
             return false;  // This is not a scenario 2 array
         }
 
-        TypeMirror propertyType = ((ArrayType) property.asType()).getComponentType();
+        TypeMirror componentType = ((ArrayType) propertyType).getComponentType();
 
         CodeBlock.Builder builder = CodeBlock.builder();
-        if (!tryArrayForLoopAssign(builder, fieldName, propertyName, propertyType)             // 2a
-                && !tryArrayUseDirectly(builder, fieldName, propertyName, propertyType)        // 2b
+        if (!tryArrayForLoopAssign(builder, fieldName, propertyName, componentType)           // 2a
+                && !tryArrayUseDirectly(builder, fieldName, propertyName, componentType)      // 2b
                 && !tryArrayForLoopCallFromGenericDocument(
-                builder, fieldName, propertyName, propertyType)) {                     // 2c
+                builder, fieldName, propertyName, componentType)) {                     // 2c
             // Scenario 2x
             throw new ProcessingException(
-                    "Unhandled in property type (2x): " + property.asType().toString(), property);
+                    "Unhandled in property type (2x): " + propertyType.toString(), property);
         }
 
         method.addCode(builder.build());
@@ -587,15 +589,16 @@
             @NonNull MethodSpec.Builder method,
             @NonNull String fieldName,
             @NonNull String propertyName,
-            @NonNull VariableElement property) throws ProcessingException {
+            @NonNull Element property) throws ProcessingException {
         // TODO(b/156296904): Handle scenario 3c (FieldCallToGenericDocument)
+        TypeMirror propertyType = getPropertyType(property);
         CodeBlock.Builder builder = CodeBlock.builder();
         if (!tryFieldUseDirectlyWithNullCheck(
-                builder, fieldName, propertyName, property.asType())  // 3a
+                builder, fieldName, propertyName, propertyType)  // 3a
                 && !tryFieldUseDirectlyWithoutNullCheck(
-                builder, fieldName, propertyName, property.asType()) // 3b
+                builder, fieldName, propertyName, propertyType) // 3b
                 && !tryFieldCallFromGenericDocument(
-                builder, fieldName, propertyName, property.asType())) {   // 3c
+                builder, fieldName, propertyName, propertyType)) {   // 3c
             throw new ProcessingException("Unhandled property type.", property);
         }
         method.addCode(builder.build());
@@ -796,11 +799,11 @@
 
     @Nullable
     private CodeBlock createAppSearchFieldWrite(@NonNull String fieldName) {
-        switch (Objects.requireNonNull(mModel.getFieldWriteKind(fieldName))) {
+        switch (Objects.requireNonNull(mModel.getElementWriteKind(fieldName))) {
             case FIELD:
                 return CodeBlock.of("document.$N = $NConv", fieldName, fieldName);
             case SETTER:
-                String setter = mModel.getSetterForField(fieldName).getSimpleName().toString();
+                String setter = mModel.getSetterForElement(fieldName).getSimpleName().toString();
                 return CodeBlock.of("document.$N($NConv)", setter, fieldName);
             default:
                 return null;  // Constructor params should already have been set
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
index 5d08981..f289cb3 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
@@ -29,19 +29,22 @@
 import java.util.Collection;
 import java.util.Deque;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.ArrayType;
 import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.Elements;
@@ -126,9 +129,24 @@
         return null;
     }
 
-    /** Checks whether the property data type is one of the valid types. */
-    public boolean isFieldOfExactType(VariableElement property, TypeMirror... validTypes) {
+    /**
+     * Returns the property type of the given property. Properties are represented by an
+     * annotated Java element that is either a Java field or a getter method.
+     */
+    @NonNull
+    public static TypeMirror getPropertyType(@NonNull Element property) {
+        Objects.requireNonNull(property);
+
         TypeMirror propertyType = property.asType();
+        if (property.getKind() == ElementKind.METHOD) {
+            propertyType = ((ExecutableType) propertyType).getReturnType();
+        }
+        return propertyType;
+    }
+
+    /** Checks whether the property data type is one of the valid types. */
+    public boolean isFieldOfExactType(Element property, TypeMirror... validTypes) {
+        TypeMirror propertyType = getPropertyType(property);
         for (TypeMirror validType : validTypes) {
             if (propertyType.getKind() == TypeKind.ARRAY) {
                 if (mTypeUtils.isSameType(
@@ -140,24 +158,30 @@
                         ((DeclaredType) propertyType).getTypeArguments().get(0), validType)) {
                     return true;
                 }
-            } else if (mTypeUtils.isSameType(property.asType(), validType)) {
+            } else if (mTypeUtils.isSameType(propertyType, validType)) {
                 return true;
             }
         }
         return false;
     }
 
+    /** Checks whether the property data type is of boolean type. */
+    public boolean isFieldOfBooleanType(Element property) {
+        return isFieldOfExactType(property, mBooleanBoxType, mBooleanPrimitiveType);
+    }
+
     /**
      * Checks whether the property data class has {@code androidx.appsearch.annotation.Document
      * .DocumentProperty} annotation.
      */
-    public boolean isFieldOfDocumentType(VariableElement property) {
-        TypeMirror propertyType = property.asType();
+    public boolean isFieldOfDocumentType(Element property) {
+        TypeMirror propertyType = getPropertyType(property);
+
         AnnotationMirror documentAnnotation = null;
 
         if (propertyType.getKind() == TypeKind.ARRAY) {
             documentAnnotation = getDocumentAnnotation(
-                    mTypeUtils.asElement(((ArrayType) property.asType()).getComponentType()));
+                    mTypeUtils.asElement(((ArrayType) propertyType).getComponentType()));
         } else if (mTypeUtils.isAssignable(mTypeUtils.erasure(propertyType), mCollectionType)) {
             documentAnnotation = getDocumentAnnotation(mTypeUtils.asElement(
                     ((DeclaredType) propertyType).getTypeArguments().get(0)));
@@ -207,7 +231,9 @@
 
     /**
      * Get a list of super classes of element annotated with @Document, in order starting with the
-     * class at the top of the hierarchy and descending down the class hierarchy
+     * class at the top of the hierarchy and descending down the class hierarchy. Note that this
+     * ordering is important because super classes must appear first in the list than child classes
+     * to make property overrides work.
      */
     @NonNull
     public static List<TypeElement> generateClassHierarchy(
@@ -229,27 +255,49 @@
             }
             hierarchy.add(element);
         } else {
-            TypeElement currentClass = element;
-            while (!currentClass.getQualifiedName()
-                    .contentEquals(Object.class.getCanonicalName())) {
-                // If you inherit from an AutoValue class, you have to implement the static methods.
-                // That defeats the purpose of AutoValue
-                if (currentClass.getAnnotation(AutoValue.class) != null) {
-                    throw new ProcessingException(
-                            "A class annotated with Document cannot inherit from a class "
-                                    + "annotated with AutoValue", element);
-                }
-
-                if (getDocumentAnnotation(currentClass) != null) {
-                    hierarchy.addFirst(currentClass);
-                }
-
-                currentClass = MoreTypes.asTypeElement(currentClass.getSuperclass());
-            }
+            Set<TypeElement> visited = new HashSet<>();
+            generateClassHierarchyHelper(element, element, hierarchy, visited);
         }
         return new ArrayList<>(hierarchy);
     }
 
+    private static void generateClassHierarchyHelper(@NonNull TypeElement leafElement,
+            @NonNull TypeElement currentClass, @NonNull Deque<TypeElement> hierarchy,
+            @NonNull Set<TypeElement> visited)
+            throws ProcessingException {
+        if (currentClass.getQualifiedName().contentEquals(Object.class.getCanonicalName())) {
+            return;
+        }
+        // If you inherit from an AutoValue class, you have to implement the static methods.
+        // That defeats the purpose of AutoValue
+        if (currentClass.getAnnotation(AutoValue.class) != null) {
+            throw new ProcessingException(
+                    "A class annotated with Document cannot inherit from a class "
+                            + "annotated with AutoValue", leafElement);
+        }
+
+        // It's possible to revisit the same interface more than once, so this check exists to
+        // catch that.
+        if (visited.contains(currentClass)) {
+            return;
+        }
+        visited.add(currentClass);
+
+        if (getDocumentAnnotation(currentClass) != null) {
+            hierarchy.addFirst(currentClass);
+        }
+        TypeMirror superclass = currentClass.getSuperclass();
+        // If currentClass is an interface, then superclass could be NONE.
+        if (superclass.getKind() != TypeKind.NONE) {
+            generateClassHierarchyHelper(leafElement, MoreTypes.asTypeElement(superclass),
+                    hierarchy, visited);
+        }
+        for (TypeMirror implementedInterface : currentClass.getInterfaces()) {
+            generateClassHierarchyHelper(leafElement, MoreTypes.asTypeElement(implementedInterface),
+                    hierarchy, visited);
+        }
+    }
+
     enum PropertyClass {
         BOOLEAN_PROPERTY_CLASS("androidx.appsearch.annotation.Document.BooleanProperty"),
         BYTES_PROPERTY_CLASS("androidx.appsearch.annotation.Document.BytesProperty"),
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
index 5adef09..8473ed4 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
@@ -16,6 +16,8 @@
 
 package androidx.appsearch.compiler;
 
+import static androidx.appsearch.compiler.IntrospectionHelper.getPropertyType;
+
 import androidx.annotation.NonNull;
 
 import com.squareup.javapoet.ClassName;
@@ -27,15 +29,16 @@
 import com.squareup.javapoet.TypeSpec;
 import com.squareup.javapoet.WildcardTypeName;
 
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.VariableElement;
+import javax.lang.model.element.TypeElement;
 import javax.lang.model.type.ArrayType;
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeKind;
@@ -47,7 +50,7 @@
     private final ProcessingEnvironment mEnv;
     private final IntrospectionHelper mHelper;
     private final DocumentModel mModel;
-    private final Set<ClassName> mDocumentTypesAccumulator = new HashSet<>();
+    private final Set<ClassName> mDependencyDocumentClasses = new LinkedHashSet<>();
 
     public static void generate(
             @NonNull ProcessingEnvironment env,
@@ -89,11 +92,11 @@
                         .addStatement("return $L", schemaInitializer)
                         .build());
 
-        classBuilder.addMethod(createNestedClassesMethod());
+        classBuilder.addMethod(createDependencyClassesMethod());
     }
 
     @NonNull
-    private MethodSpec createNestedClassesMethod() {
+    private MethodSpec createDependencyClassesMethod() {
         TypeName setOfClasses = ParameterizedTypeName.get(ClassName.get("java.util", "List"),
                 ParameterizedTypeName.get(ClassName.get(Class.class),
                         WildcardTypeName.subtypeOf(Object.class)));
@@ -103,18 +106,18 @@
                         ParameterizedTypeName.get(ClassName.get(Class.class),
                                 WildcardTypeName.subtypeOf(Object.class)));
 
-        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getNestedDocumentClasses")
+        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getDependencyDocumentClasses")
                 .addModifiers(Modifier.PUBLIC)
                 .returns(setOfClasses)
                 .addAnnotation(Override.class)
                 .addException(mHelper.getAppSearchExceptionClass());
 
-        if (mDocumentTypesAccumulator.isEmpty()) {
+        if (mDependencyDocumentClasses.isEmpty()) {
             methodBuilder.addStatement("return $T.emptyList()",
                     ClassName.get("java.util", "Collections"));
         } else {
             methodBuilder.addStatement("$T classSet = new $T()", setOfClasses, arraySetOfClasses);
-            for (ClassName className : mDocumentTypesAccumulator) {
+            for (ClassName className : mDependencyDocumentClasses) {
                 methodBuilder.addStatement("classSet.add($T.class)", className);
             }
             methodBuilder.addStatement("return classSet").build();
@@ -123,55 +126,59 @@
     }
 
     /**
-     * This method accumulates Document-type properties in mDocumentTypesAccumulator by calling
-     * {@link #createPropertySchema}.
+     * This method accumulates Document-type properties, by calling {@link #createPropertySchema},
+     * and parent types in mDependencyDocumentClasses.
      */
     private CodeBlock createSchemaInitializerGetDocumentTypes() throws ProcessingException {
         CodeBlock.Builder codeBlock = CodeBlock.builder()
                 .add("new $T(SCHEMA_NAME)", mHelper.getAppSearchClass("AppSearchSchema", "Builder"))
                 .indent();
-        for (VariableElement property : mModel.getPropertyFields().values()) {
+        for (TypeElement parentType : mModel.getParentTypes()) {
+            ClassName parentDocumentFactoryClass =
+                    mHelper.getDocumentClassFactoryForClass(ClassName.get(parentType));
+            codeBlock.add("\n.addParentType($T.SCHEMA_NAME)", parentDocumentFactoryClass);
+            mDependencyDocumentClasses.add(ClassName.get(parentType));
+        }
+        for (Element property : mModel.getPropertyElements().values()) {
             codeBlock.add("\n.addProperty($L)", createPropertySchema(property));
         }
         codeBlock.add("\n.build()").unindent();
         return codeBlock.build();
     }
 
-    /** This method accumulates Document-type properties in mDocumentTypesAccumulator. */
-    private CodeBlock createPropertySchema(@NonNull VariableElement property)
+    /** This method accumulates Document-type properties in mDependencyDocumentClasses. */
+    private CodeBlock createPropertySchema(@NonNull Element property)
             throws ProcessingException {
         AnnotationMirror annotation = mModel.getPropertyAnnotation(property);
         Map<String, Object> params = mHelper.getAnnotationParams(annotation);
 
         // Find the property type
         Types typeUtil = mEnv.getTypeUtils();
-        TypeMirror propertyType;
+        TypeMirror propertyType = getPropertyType(property);
         boolean repeated = false;
         boolean isPropertyString = false;
         boolean isPropertyDocument = false;
         boolean isPropertyLong = false;
-        if (property.asType().getKind() == TypeKind.ERROR) {
+        if (propertyType.getKind() == TypeKind.ERROR) {
             throw new ProcessingException("Property type unknown to java compiler", property);
         } else if (typeUtil.isAssignable(
-                typeUtil.erasure(property.asType()), mHelper.mCollectionType)) {
+                typeUtil.erasure(propertyType), mHelper.mCollectionType)) {
             List<? extends TypeMirror> genericTypes =
-                    ((DeclaredType) property.asType()).getTypeArguments();
+                    ((DeclaredType) propertyType).getTypeArguments();
             if (genericTypes.isEmpty()) {
                 throw new ProcessingException(
                         "Property is repeated but has no generic type", property);
             }
             propertyType = genericTypes.get(0);
             repeated = true;
-        } else if (property.asType().getKind() == TypeKind.ARRAY
+        } else if (propertyType.getKind() == TypeKind.ARRAY
                 // Byte arrays have a native representation in Icing, so they are not considered a
                 // "repeated" type
-                && !typeUtil.isSameType(property.asType(), mHelper.mBytePrimitiveArrayType)
-                && !typeUtil.isSameType(property.asType(), mHelper.mByteBoxArrayType)) {
-            propertyType = ((ArrayType) property.asType()).getComponentType();
+                && !typeUtil.isSameType(propertyType, mHelper.mBytePrimitiveArrayType)
+                && !typeUtil.isSameType(propertyType, mHelper.mByteBoxArrayType)) {
+            propertyType = ((ArrayType) propertyType).getComponentType();
             repeated = true;
 
-        } else {
-            propertyType = property.asType();
         }
         ClassName propertyClass;
         if (typeUtil.isSameType(propertyType, mHelper.mStringType)) {
@@ -210,7 +217,7 @@
                     propertyClass.nestedClass("Builder"),
                     propertyName,
                     documentFactoryClass);
-            mDocumentTypesAccumulator.add(documentClass);
+            mDependencyDocumentClasses.add(documentClass);
         } else {
             codeBlock.add("new $T($S)", propertyClass.nestedClass("Builder"), propertyName);
         }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
index a44867a..c746d9e 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
@@ -17,6 +17,7 @@
 package androidx.appsearch.compiler;
 
 import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentAnnotation;
+import static androidx.appsearch.compiler.IntrospectionHelper.getPropertyType;
 
 import androidx.annotation.NonNull;
 
@@ -34,7 +35,6 @@
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.ArrayType;
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeKind;
@@ -93,7 +93,7 @@
         setSpecialFields(methodBuilder);
 
         // Set properties
-        for (Map.Entry<String, VariableElement> entry : mModel.getPropertyFields().entrySet()) {
+        for (Map.Entry<String, Element> entry : mModel.getPropertyElements().entrySet()) {
             fieldToGenericDoc(methodBuilder, entry.getKey(), entry.getValue());
         }
 
@@ -108,7 +108,7 @@
     private void fieldToGenericDoc(
             @NonNull MethodSpec.Builder method,
             @NonNull String fieldName,
-            @NonNull VariableElement property) throws ProcessingException {
+            @NonNull Element property) throws ProcessingException {
         // Scenario 1: field is a Collection
         //   1a: CollectionForLoopAssign
         //       Collection contains boxed Long, Integer, Double, Float, Boolean or byte[].
@@ -192,9 +192,10 @@
             @NonNull MethodSpec.Builder method,
             @NonNull String fieldName,
             @NonNull String propertyName,
-            @NonNull VariableElement property) throws ProcessingException {
+            @NonNull Element property) throws ProcessingException {
         Types typeUtil = mEnv.getTypeUtils();
-        if (!typeUtil.isAssignable(typeUtil.erasure(property.asType()), mHelper.mCollectionType)) {
+        TypeMirror propertyType = getPropertyType(property);
+        if (!typeUtil.isAssignable(typeUtil.erasure(propertyType), mHelper.mCollectionType)) {
             return false;  // This is not a scenario 1 collection
         }
 
@@ -202,21 +203,20 @@
         CodeBlock.Builder body = CodeBlock.builder()
                 .addStatement(
                         "$T $NCopy = $L",
-                        property.asType(),
+                        propertyType,
                         fieldName,
                         createAppSearchFieldRead(fieldName));
 
-        List<? extends TypeMirror> genericTypes =
-                ((DeclaredType) property.asType()).getTypeArguments();
-        TypeMirror propertyType = genericTypes.get(0);
+        List<? extends TypeMirror> genericTypes = ((DeclaredType) propertyType).getTypeArguments();
+        TypeMirror componentType = genericTypes.get(0);
 
-        if (!tryCollectionForLoopAssign(body, fieldName, propertyName, propertyType)           // 1a
-                && !tryCollectionCallToArray(body, fieldName, propertyName, propertyType)      // 1b
+        if (!tryCollectionForLoopAssign(body, fieldName, propertyName, componentType)         // 1a
+                && !tryCollectionCallToArray(body, fieldName, propertyName, componentType)    // 1b
                 && !tryCollectionForLoopCallToGenericDocument(
-                body, fieldName, propertyName, propertyType)) {                        // 1c
+                body, fieldName, propertyName, componentType)) {                        // 1c
             // Scenario 1x
             throw new ProcessingException(
-                    "Unhandled out property type (1x): " + property.asType().toString(), property);
+                    "Unhandled out property type (1x): " + propertyType.toString(), property);
         }
 
         method.addCode(body.build());
@@ -359,12 +359,13 @@
             @NonNull MethodSpec.Builder method,
             @NonNull String fieldName,
             @NonNull String propertyName,
-            @NonNull VariableElement property) throws ProcessingException {
+            @NonNull Element property) throws ProcessingException {
         Types typeUtil = mEnv.getTypeUtils();
-        if (property.asType().getKind() != TypeKind.ARRAY
+        TypeMirror propertyType = getPropertyType(property);
+        if (propertyType.getKind() != TypeKind.ARRAY
                 // Byte arrays have a native representation in Icing, so they are not considered a
                 // "repeated" type
-                || typeUtil.isSameType(property.asType(), mHelper.mBytePrimitiveArrayType)) {
+                || typeUtil.isSameType(propertyType, mHelper.mBytePrimitiveArrayType)) {
             return false;  // This is not a scenario 2 array
         }
 
@@ -372,19 +373,19 @@
         CodeBlock.Builder body = CodeBlock.builder()
                 .addStatement(
                         "$T $NCopy = $L",
-                        property.asType(),
+                        propertyType,
                         fieldName,
                         createAppSearchFieldRead(fieldName));
 
-        TypeMirror propertyType = ((ArrayType) property.asType()).getComponentType();
+        TypeMirror componentType = ((ArrayType) propertyType).getComponentType();
 
-        if (!tryArrayForLoopAssign(body, fieldName, propertyName, propertyType)                // 2a
-                && !tryArrayUseDirectly(body, fieldName, propertyName, propertyType)           // 2b
+        if (!tryArrayForLoopAssign(body, fieldName, propertyName, componentType)              // 2a
+                && !tryArrayUseDirectly(body, fieldName, propertyName, componentType)         // 2b
                 && !tryArrayForLoopCallToGenericDocument(
-                body, fieldName, propertyName, propertyType)) {                        // 2c
+                body, fieldName, propertyName, componentType)) {                        // 2c
             // Scenario 2x
             throw new ProcessingException(
-                    "Unhandled out property type (2x): " + property.asType().toString(), property);
+                    "Unhandled out property type (2x): " + propertyType.toString(), property);
         }
 
         method.addCode(body.build());
@@ -533,15 +534,16 @@
             @NonNull MethodSpec.Builder method,
             @NonNull String fieldName,
             @NonNull String propertyName,
-            @NonNull VariableElement property) throws ProcessingException {
+            @NonNull Element property) throws ProcessingException {
         // TODO(b/156296904): Handle scenario 3c (FieldCallToGenericDocument)
+        TypeMirror propertyType = getPropertyType(property);
         CodeBlock.Builder body = CodeBlock.builder();
         if (!tryFieldUseDirectlyWithNullCheck(
-                body, fieldName, propertyName, property.asType())  // 3a
+                body, fieldName, propertyName, propertyType)  // 3a
                 && !tryFieldUseDirectlyWithoutNullCheck(
-                body, fieldName, propertyName, property.asType())  // 3b
+                body, fieldName, propertyName, propertyType)  // 3b
                 && !tryFieldCallToGenericDocument(
-                body, fieldName, propertyName, property.asType())) {  // 3c
+                body, fieldName, propertyName, propertyType)) {  // 3c
             throw new ProcessingException("Unhandled property type.", property);
         }
         method.addCode(body.build());
@@ -689,11 +691,11 @@
     }
 
     private CodeBlock createAppSearchFieldRead(@NonNull String fieldName) {
-        switch (Objects.requireNonNull(mModel.getFieldReadKind(fieldName))) {
+        switch (Objects.requireNonNull(mModel.getElementReadKind(fieldName))) {
             case FIELD:
                 return CodeBlock.of("document.$N", fieldName);
             case GETTER:
-                String getter = mModel.getGetterForField(fieldName).getSimpleName().toString();
+                String getter = mModel.getGetterForElement(fieldName).getSimpleName().toString();
                 return CodeBlock.of("document.$N()", getter);
         }
         return null;
diff --git a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
index 43e24214..de6e59c 100644
--- a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
+++ b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
@@ -57,16 +57,6 @@
     }
 
     @Test
-    public void testNonClass() {
-        Compilation compilation = compile(
-                "@Document\n"
-                        + "public interface Gift {}\n");
-
-        assertThat(compilation).hadErrorContaining(
-                "annotation on something other than a class");
-    }
-
-    @Test
     public void testPrivate() {
         Compilation compilation = compile(
                 "Wrapper",
@@ -199,28 +189,6 @@
         assertThat(nonAnnotatedFieldHasSameName).hadErrorContaining(
                 "Non-annotated field overriding special annotated fields named: id");
 
-        Compilation propertyCollision = compile(
-                "@Document\n"
-                        + "public class Gift {\n"
-                        + "  @Document.Namespace String namespace;\n"
-                        + "  @Document.Id String id;\n"
-                        + "  @Document.StringProperty String prop;\n"
-                        + "  Gift(String id, String namespace, String prop) {\n"
-                        + "    this.id = id;\n"
-                        + "    this.namespace = namespace;\n"
-                        + "    this.prop = prop;\n"
-                        + "  }\n"
-                        + "}\n"
-                        + "@Document\n"
-                        + "class CoolGift extends Gift {\n"
-                        + "  @Document.BooleanProperty Boolean prop;\n"
-                        + "  CoolGift(String id, String namespace, String prop) {\n"
-                        + "    super(id, namespace, prop);\n"
-                        + "  }\n"
-                        + "}\n");
-        assertThat(propertyCollision).hadErrorContaining(
-                "Class hierarchy contains multiple annotated fields named: prop");
-
         //error on collision
         Compilation idCollision = compile(
                 "@Document\n"
@@ -548,20 +516,6 @@
     }
 
     @Test
-    public void testPropertyOnFieldForNonAutoValueClass() {
-        Compilation compilation = compile(
-                "@Document\n"
-                        + "public class Gift {\n"
-                        + "  @Document.Namespace String namespace;\n"
-                        + "  @Document.Id String id;\n"
-                        + "  @Document.LongProperty private int getPrice() { return 0; }\n"
-                        + "}\n");
-
-        assertThat(compilation).hadErrorContaining(
-                "AppSearch annotation is not applicable to methods for Non-AutoValue class");
-    }
-
-    @Test
     public void testCantRead_noGetter() {
         Compilation compilation = compile(
                 "@Document\n"
@@ -698,7 +652,7 @@
                 "Failed to find any suitable creation methods to build class "
                         + "\"com.example.appsearch.Gift\"");
         assertThat(compilation).hadWarningContainingMatch(
-                "Field cannot be written .* failed to find a suitable setter for field \"price\"");
+                "Field cannot be written .* failed to find a suitable setter for \"price\"");
         assertThat(compilation).hadWarningContaining(
                 "Cannot use this creation method to construct the class: This method doesn't have "
                         + "parameters for the following fields: [price]");
@@ -720,7 +674,7 @@
                 "Failed to find any suitable creation methods to build class "
                         + "\"com.example.appsearch.Gift\"");
         assertThat(compilation).hadWarningContainingMatch(
-                "Field cannot be written .* failed to find a suitable setter for field \"price\"");
+                "Field cannot be written .* failed to find a suitable setter for \"price\"");
         assertThat(compilation).hadWarningContaining(
                 "Setter cannot be used: private visibility");
         assertThat(compilation).hadWarningContaining(
@@ -744,7 +698,7 @@
                 "Failed to find any suitable creation methods to build class "
                         + "\"com.example.appsearch.Gift\"");
         assertThat(compilation).hadWarningContainingMatch(
-                "Field cannot be written .* failed to find a suitable setter for field \"price\"");
+                "Field cannot be written .* failed to find a suitable setter for \"price\"");
         assertThat(compilation).hadWarningContaining(
                 "Setter cannot be used: takes 0 parameters instead of 1");
         assertThat(compilation).hadWarningContaining(
@@ -1574,6 +1528,630 @@
                 /* content= */ "return Collections.emptyList();");
     }
 
+    @Test
+    public void testPolymorphism() throws Exception {
+        // Gift should automatically get "note2" via Java's "extends" semantics, but "note1" need
+        // to be manually provided so that Parent1 can be a parent of Gift.
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Parent1 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Parent2 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note2;\n"
+                        + "}\n"
+                        + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n"
+                        + "class Gift extends Parent2 {\n"
+                        + "  @Document.StringProperty String sender;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        checkResultContains("Gift.java", "addParentType($$__AppSearch__Parent1.SCHEMA_NAME)");
+        checkResultContains("Gift.java", "addParentType($$__AppSearch__Parent2.SCHEMA_NAME)");
+
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testPolymorphismOverrideExtendedProperty() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Parent1 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Parent2 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty(indexingType=2) String note2;\n"
+                        + "}\n"
+                        + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n"
+                        + "class Gift extends Parent2 {\n"
+                        + "  @Document.StringProperty String sender;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "  @Document.StringProperty(indexingType=1) String note2;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        // Should expect the indexingType of note2 from Gift is 1, which is
+        // INDEXING_TYPE_EXACT_TERMS, instead of 2.
+        checkResultContains("Gift.java",
+                "setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)");
+
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testPolymorphismOverrideExtendedPropertyInvalid() throws Exception {
+        // Overridden properties cannot change the names.
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Parent1 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Parent2 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty(name=\"note2\", indexingType=2) String "
+                        + "note2;\n"
+                        + "}\n"
+                        + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n"
+                        + "class Gift extends Parent2 {\n"
+                        + "  @Document.StringProperty String sender;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "  @Document.StringProperty(name=\"note2_new\", indexingType=1) String "
+                        + "note2;\n"
+                        + "}\n");
+        assertThat(compilation).hadErrorContaining(
+                "Cannot override a property with a different name");
+
+        // Overridden properties cannot change the types.
+        compilation = compile(
+                "@Document\n"
+                        + "class Parent1 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Parent2 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note2;\n"
+                        + "}\n"
+                        + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n"
+                        + "class Gift extends Parent2 {\n"
+                        + "  @Document.StringProperty String sender;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "  @LongProperty Long note2;\n"
+                        + "}\n");
+        assertThat(compilation).hadErrorContaining(
+                "Cannot override a property with a different type");
+    }
+
+    @Test
+    public void testPolymorphismWithNestedType() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Parent1 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Parent2 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note2;\n"
+                        + "}\n"
+                        + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n"
+                        + "class Gift extends Parent2 {\n"
+                        + "  @Document.StringProperty String sender;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "  @Document.DocumentProperty Inner innerContent;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Inner {\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.StringProperty String contents;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        // Should see that both the parent types and nested types are added to the generated
+        // getDependencyDocumentClasses method in Gift.
+        checkResultContains("Gift.java", "classSet.add(Parent1.class)");
+        checkResultContains("Gift.java", "classSet.add(Parent2.class)");
+        checkResultContains("Gift.java", "classSet.add(Inner.class)");
+
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testPolymorphismDuplicatedParents() throws Exception {
+        // Should see that every parent can only be added once.
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Parent1 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Parent2 {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note2;\n"
+                        + "}\n"
+                        + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class, "
+                        + "Parent1.class})\n"
+                        + "class Gift extends Parent2 {\n"
+                        + "  @Document.StringProperty String sender;\n"
+                        + "  @Document.StringProperty String note1;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testPolymorphismChildTypeWithoutName() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Parent {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String note;\n"
+                        + "}\n"
+                        + "@Document(parent = Parent.class)\n"
+                        + "class Gift extends Parent {\n"
+                        + "  @Document.StringProperty String sender;\n"
+                        + "}\n");
+        assertThat(compilation).hadErrorContaining(
+                "All @Document classes with a parent must explicitly provide a name");
+    }
+
+    @Test
+    public void testAnnotationOnClassGetter() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty public int getPrice() { return 0; }\n"
+                        + "  public void setPrice(int price) {}\n"
+                        + "}\n");
+
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")");
+        checkResultContains("Gift.java", "document.setPrice(getPriceConv)");
+        checkResultContains("Gift.java", "document.getPrice()");
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testAnnotationOnClassGetterUsingFactory() throws IOException {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  private Gift() {}\n"
+                        + "  public static Gift create(String id, String namespace, int price) {\n"
+                        + "    return new Gift();\n"
+                        + "  }\n"
+                        + "  @Document.Namespace public String getNamespace() { return \"hi\"; }\n"
+                        + "  @Document.Id public String getId() { return \"0\"; }\n"
+                        + "  @Document.LongProperty public int getPrice() { return 0; }\n"
+                        + "}\n");
+
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")");
+        checkResultContains("Gift.java", "Gift.create(getIdConv, getNamespaceConv, getPriceConv)");
+        checkResultContains("Gift.java", "document.getPrice()");
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testAnnotationOnInterfaceGetter() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public interface Gift {\n"
+                        + "  public static Gift create(String id, String namespace) {\n"
+                        + "    return new GiftImpl(id, namespace);\n"
+                        + "  }\n"
+                        + "  @Document.Namespace public String getNamespace();\n"
+                        + "  @Document.Id public String getId();\n"
+                        + "  @Document.LongProperty public int getPrice();\n"
+                        + "  public void setPrice(int price);\n"
+                        + "}\n"
+                        + "class GiftImpl implements Gift{\n"
+                        + "  public GiftImpl(String id, String namespace) {\n"
+                        + "    this.id = id;\n"
+                        + "    this.namespace = namespace;\n"
+                        + "  }\n"
+                        + "  private String namespace;\n"
+                        + "  private String id;\n"
+                        + "  private int price;\n"
+                        + "  public String getNamespace() { return namespace; }\n"
+                        + "  public String getId() { return id; }\n"
+                        + "  public int getPrice() { return price; }\n"
+                        + "  public void setPrice(int price) { this.price = price; }\n"
+                        + "}\n");
+
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")");
+        checkResultContains("Gift.java", "Gift.create(getIdConv, getNamespaceConv)");
+        checkResultContains("Gift.java", "document.setPrice(getPriceConv)");
+        checkResultContains("Gift.java", "document.getPrice()");
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testAnnotationOnGetterWithoutFactory() throws Exception {
+        // An interface without any factory method is not able to initialize, as interfaces do
+        // not have constructors.
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public interface Gift {\n"
+                        + "  @Document.Namespace public String getNamespace();\n"
+                        + "  @Document.Id public String getId();\n"
+                        + "  @Document.LongProperty public int getPrice();\n"
+                        + "  public void setPrice(int price);\n"
+                        + "}\n");
+
+        assertThat(compilation).hadErrorContaining("Failed to find any suitable creation methods");
+    }
+
+    @Test
+    public void testAnnotationOnGetterWithoutSetter() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public interface Gift {\n"
+                        + "  public static Gift create(String id, String namespace) {\n"
+                        + "    return new GiftImpl(id, namespace);\n"
+                        + "  }\n"
+                        + "  @Document.Namespace public String getNamespace();\n"
+                        + "  @Document.Id public String getId();\n"
+                        + "  @Document.LongProperty public int getPrice();\n"
+                        + "}\n"
+                        + "class GiftImpl implements Gift{\n"
+                        + "  public GiftImpl(String id, String namespace) {\n"
+                        + "    this.id = id;\n"
+                        + "    this.namespace = namespace;\n"
+                        + "  }\n"
+                        + "  private String namespace;\n"
+                        + "  private String id;\n"
+                        + "  private int price;\n"
+                        + "  public String getNamespace() { return namespace; }\n"
+                        + "  public String getId() { return id; }\n"
+                        + "  public int getPrice() { return price; }\n"
+                        + "}\n");
+
+        assertThat(compilation).hadWarningContaining(
+                "Element cannot be written directly because it is an annotated getter");
+    }
+
+    @Test
+    public void testInterfaceImplementingParents() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "interface Root {\n"
+                        + "  @Document.Namespace public String getNamespace();\n"
+                        + "  @Document.Id public String getId();\n"
+                        + "  public static Root create(String id, String namespace) {\n"
+                        + "    return new GiftImpl();\n"
+                        + "  }\n"
+                        + "}\n"
+                        + "@Document(name=\"Parent1\", parent=Root.class)\n"
+                        + "interface Parent1 extends Root {\n"
+                        + "  @Document.StringProperty public String getStr1();\n"
+                        + "  public static Parent1 create(String id, String namespace, String "
+                        + "str1) {\n"
+                        + "    return new GiftImpl();\n"
+                        + "  }"
+                        + "}\n"
+                        + "@Document(name=\"Parent2\", parent=Root.class)\n"
+                        + "interface Parent2 extends Root {\n"
+                        + "  @Document.StringProperty public String getStr2();\n"
+                        + "  public static Parent2 create(String id, String namespace, String "
+                        + "str2) {\n"
+                        + "    return new GiftImpl();\n"
+                        + "  }\n"
+                        + "}\n"
+                        + "@Document(name=\"Gift\", parent={Parent1.class, Parent2.class})\n"
+                        + "public interface Gift extends Parent1, Parent2 {\n"
+                        + "  public static Gift create(String id, String namespace, String str1, "
+                        + "String str2, int price) {\n"
+                        + "    return new GiftImpl();\n"
+                        + "  }\n"
+                        + "  @Document.LongProperty public int getPrice();\n"
+                        + "}\n"
+                        + "class GiftImpl implements Gift{\n"
+                        + "  public GiftImpl() {}\n"
+                        + "  public String getNamespace() { return \"namespace\"; }\n"
+                        + "  public String getId() { return \"id\"; }\n"
+                        + "  public String getStr1() { return \"str1\"; }\n"
+                        + "  public String getStr2() { return \"str2\"; }\n"
+                        + "  public int getPrice() { return 0; }\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.StringPropertyConfig.Builder(\"str1\")");
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.StringPropertyConfig.Builder(\"str2\")");
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")");
+        checkResultContains("Gift.java",
+                "Gift.create(getIdConv, getNamespaceConv, getStr1Conv, getStr2Conv, getPriceConv)");
+        checkResultContains("Gift.java", "document.getStr1()");
+        checkResultContains("Gift.java", "document.getStr2()");
+        checkResultContains("Gift.java", "document.getPrice()");
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testSameNameGetterAndFieldAnnotatingGetter() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty public int getPrice() { return 0; }\n"
+                        + "  public int price;\n"
+                        + "  public void setPrice(int price) {}\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")");
+        checkResultContains("Gift.java", "document.setPrice(getPriceConv)");
+        checkResultContains("Gift.java", "document.getPrice()");
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testSameNameGetterAndFieldAnnotatingField() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty public int price;\n"
+                        + "  public int getPrice() { return 0; }\n"
+                        + "  public void setPrice(int price) {}\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")");
+        checkResultContains("Gift.java", "document.price = priceConv");
+        checkResultContains("Gift.java",
+                "builder.setPropertyLong(\"price\", document.price)");
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testSameNameGetterAndFieldAnnotatingBoth() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty(name=\"price1\")\n"
+                        + "  public int getPrice() { return 0; }\n"
+                        + "  public void setPrice(int price) {}\n"
+                        + "  @Document.LongProperty(name=\"price2\")\n"
+                        + "  public int price;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price1\")");
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price2\")");
+        checkResultContains("Gift.java", "document.setPrice(getPriceConv)");
+        checkResultContains("Gift.java", "document.price = priceConv");
+        checkResultContains("Gift.java",
+                "builder.setPropertyLong(\"price1\", document.getPrice())");
+        checkResultContains("Gift.java",
+                "builder.setPropertyLong(\"price2\", document.price)");
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testSameNameGetterAndFieldAnnotatingBothButGetterIsPrivate() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty(name=\"price1\")\n"
+                        + "  private int getPrice() { return 0; }\n"
+                        + "  public void setPrice(int price) {}\n"
+                        + "  @Document.LongProperty(name=\"price2\")\n"
+                        + "  public int price;\n"
+                        + "}\n");
+        assertThat(compilation).hadErrorContaining(
+                "Failed to find a suitable getter for element \"getPrice\"");
+        assertThat(compilation).hadWarningContaining(
+                "Getter cannot be used: private visibility");
+    }
+
+    @Test
+    public void testSameNameGetterAndFieldAnnotatingBothWithDifferentType() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.DoubleProperty(name=\"price1\")\n"
+                        + "  public double getPrice() { return 0.2; }\n"
+                        + "  public void setPrice(double price) {}\n"
+                        + "  @Document.LongProperty(name=\"price2\")\n"
+                        + "  public int price;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.DoublePropertyConfig.Builder(\"price1\")");
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price2\")");
+        checkResultContains("Gift.java", "document.setPrice(getPriceConv)");
+        checkResultContains("Gift.java", "document.price = priceConv");
+        checkResultContains("Gift.java",
+                "builder.setPropertyDouble(\"price1\", document.getPrice())");
+        checkResultContains("Gift.java",
+                "builder.setPropertyLong(\"price2\", document.price)");
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testSameNameGetterAndFieldAnnotatingBothWithoutSetter() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty(name=\"price1\")\n"
+                        + "  public int getPrice() { return 0; }\n"
+                        + "  @Document.LongProperty(name=\"price2\")\n"
+                        + "  public int price;\n"
+                        + "}\n");
+        // Cannot find a setter method for the price1 field.
+        assertThat(compilation).hadErrorContaining(
+                "Failed to find any suitable creation methods");
+    }
+
+    @Test
+    public void testNameNormalization() throws Exception {
+        // getMPrice should correspond to a field named "mPrice"
+        // mPrice should correspond to a field named "price"
+        // isSold should correspond to a field named "sold"
+        // mx should correspond to a field named "mx"
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty\n"
+                        + "  public int getMPrice() { return 0; }\n"
+                        + "  public void setMPrice(int price) {}\n"
+                        + "  @Document.LongProperty\n"
+                        + "  public int mPrice;\n"
+                        + "  @Document.BooleanProperty\n"
+                        + "  public boolean isSold() { return false; }\n"
+                        + "  public void setSold(boolean sold) {}\n"
+                        + "  @Document.LongProperty\n"
+                        + "  public int mx() { return 0; }\n"
+                        + "  public void setMx(int x) {}\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"mPrice\")");
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")");
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.BooleanPropertyConfig.Builder(\"sold\")");
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"mx\")");
+
+        checkResultContains("Gift.java", "document.setMPrice(getMPriceConv)");
+        checkResultContains("Gift.java", "document.mPrice = mPriceConv");
+        checkResultContains("Gift.java", "document.setSold(isSoldConv)");
+        checkResultContains("Gift.java", "document.setMx(mxConv)");
+
+        checkResultContains("Gift.java",
+                "builder.setPropertyLong(\"mPrice\", document.getMPrice())");
+        checkResultContains("Gift.java",
+                "builder.setPropertyLong(\"price\", document.mPrice)");
+        checkResultContains("Gift.java",
+                "builder.setPropertyBoolean(\"sold\", document.isSold())");
+        checkResultContains("Gift.java",
+                "builder.setPropertyLong(\"mx\", document.mx())");
+
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testGetterWithParameterCannotBeUsed() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty\n"
+                        + "  public int getPrice(int price) { return 0; }\n"
+                        + "  public void setPrice(int price) {}\n"
+                        + "}\n");
+        assertThat(compilation).hadErrorContaining(
+                "Failed to find a suitable getter for element \"getPrice\"");
+        assertThat(compilation).hadWarningContaining(
+                "Getter cannot be used: should take no parameters");
+    }
+
+    @Test
+    public void testPrivateGetterCannotBeUsed() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty\n"
+                        + "  private int getPrice() { return 0; }\n"
+                        + "  public void setPrice(int price) {}\n"
+                        + "}\n");
+        assertThat(compilation).hadErrorContaining(
+                "Failed to find a suitable getter for element \"getPrice\"");
+        assertThat(compilation).hadWarningContaining(
+                "Getter cannot be used: private visibility");
+    }
+
+    @Test
+    public void testOverloadedGetterIsOk() throws Exception {
+        // Overloaded getter should be ok because annotation processor will find the correct getter
+        // that can be used.
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  public int getPrice(int price) { return 0; }\n"
+                        + "  @Document.LongProperty\n"
+                        + "  public int getPrice() { return 0; }\n"
+                        + "  public void setPrice(int price) {}\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultContains("Gift.java",
+                "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")");
+        checkResultContains("Gift.java", "document.setPrice(getPriceConv)");
+        checkResultContains("Gift.java", "document.getPrice()");
+        checkEqualsGolden("Gift.java");
+    }
+
+    @Test
+    public void testGetterWithWrongReturnType() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty\n"
+                        + "  private int getPrice() { return 0; }\n"
+                        + "  public void setPrice(int price) {}\n"
+                        + "}\n");
+        assertThat(compilation).hadErrorContaining(
+                "Document.StringProperty doesn't accept the data type of property field getPrice");
+    }
+
     private Compilation compile(String classBody) {
         return compile("Gift", classBody);
     }
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA
index a49931b..f4a7455 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA
@@ -58,7 +58,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_field.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_field.JAVA
index 3f9ad76..5b19f3e 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_field.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_field.JAVA
@@ -31,7 +31,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA
index 1d97d08..8a935c1 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA
@@ -31,7 +31,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetter.JAVA
new file mode 100644
index 0000000..b7bd374
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetter.JAVA
@@ -0,0 +1,57 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    builder.setPropertyLong("price", document.getPrice());
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    int getPriceConv = (int) genericDoc.getPropertyLong("price");
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.setPrice(getPriceConv);
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetterUsingFactory.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetterUsingFactory.JAVA
new file mode 100644
index 0000000..f711d54
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetterUsingFactory.JAVA
@@ -0,0 +1,54 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.getNamespace(), document.getId(), SCHEMA_NAME);
+    builder.setPropertyLong("price", document.getPrice());
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String getIdConv = genericDoc.getId();
+    String getNamespaceConv = genericDoc.getNamespace();
+    int getPriceConv = (int) genericDoc.getPropertyLong("price");
+    Gift document = Gift.create(getIdConv, getNamespaceConv, getPriceConv);
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnInterfaceGetter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnInterfaceGetter.JAVA
new file mode 100644
index 0000000..09ea85f
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnInterfaceGetter.JAVA
@@ -0,0 +1,55 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.getNamespace(), document.getId(), SCHEMA_NAME);
+    builder.setPropertyLong("price", document.getPrice());
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String getIdConv = genericDoc.getId();
+    String getNamespaceConv = genericDoc.getNamespace();
+    int getPriceConv = (int) genericDoc.getPropertyLong("price");
+    Gift document = Gift.create(getIdConv, getNamespaceConv);
+    document.setPrice(getPriceConv);
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocument.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocument.JAVA
index fc27579..f1e80d9 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocument.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocument.JAVA
@@ -33,7 +33,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA
index d7954e1..0805862 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA
@@ -47,7 +47,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testDifferentTypeName.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testDifferentTypeName.JAVA
index eb8b33b..0f0dae4 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testDifferentTypeName.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testDifferentTypeName.JAVA
@@ -27,7 +27,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGetterAndSetterFunctions_withFieldName.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGetterAndSetterFunctions_withFieldName.JAVA
index 3096bdc..ed92dfe 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGetterAndSetterFunctions_withFieldName.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGetterAndSetterFunctions_withFieldName.JAVA
@@ -31,7 +31,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA
index acad761..049d3ba 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA
@@ -45,7 +45,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA
index d55520d..3d84a6a 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA
@@ -33,7 +33,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceImplementingParents.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceImplementingParents.JAVA
new file mode 100644
index 0000000..54f94c9
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceImplementingParents.JAVA
@@ -0,0 +1,89 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent1.SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent2.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("str2")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("str1")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    List<Class<?>> classSet = new ArrayList<Class<?>>();
+    classSet.add(Parent1.class);
+    classSet.add(Parent2.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.getNamespace(), document.getId(), SCHEMA_NAME);
+    String getStr2Copy = document.getStr2();
+    if (getStr2Copy != null) {
+      builder.setPropertyString("str2", getStr2Copy);
+    }
+    String getStr1Copy = document.getStr1();
+    if (getStr1Copy != null) {
+      builder.setPropertyString("str1", getStr1Copy);
+    }
+    builder.setPropertyLong("price", document.getPrice());
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String getIdConv = genericDoc.getId();
+    String getNamespaceConv = genericDoc.getNamespace();
+    String[] getStr2Copy = genericDoc.getPropertyStringArray("str2");
+    String getStr2Conv = null;
+    if (getStr2Copy != null && getStr2Copy.length != 0) {
+      getStr2Conv = getStr2Copy[0];
+    }
+    String[] getStr1Copy = genericDoc.getPropertyStringArray("str1");
+    String getStr1Conv = null;
+    if (getStr1Copy != null && getStr1Copy.length != 0) {
+      getStr1Conv = getStr1Copy[0];
+    }
+    int getPriceConv = (int) genericDoc.getPropertyLong("price");
+    Gift document = Gift.create(getIdConv, getNamespaceConv, getStr1Conv, getStr2Conv, getPriceConv);
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA
index abe3acb..64e240a 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA
@@ -69,7 +69,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNestedAutoValueDocument.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNestedAutoValueDocument.JAVA
index 856821f..c372421 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNestedAutoValueDocument.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNestedAutoValueDocument.JAVA
@@ -27,7 +27,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNesting.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNesting.JAVA
index 7d8acd0..2dfd06b 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNesting.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNesting.JAVA
@@ -35,7 +35,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     List<Class<?>> classSet = new ArrayList<Class<?>>();
     classSet.add(Middle.class);
     return classSet;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNameNormalization.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNameNormalization.JAVA
new file mode 100644
index 0000000..2bca453
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNameNormalization.JAVA
@@ -0,0 +1,77 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("mPrice")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.BooleanPropertyConfig.Builder("sold")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .build())
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("mx")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    builder.setPropertyLong("mPrice", document.getMPrice());
+    builder.setPropertyLong("price", document.mPrice);
+    builder.setPropertyBoolean("sold", document.isSold());
+    builder.setPropertyLong("mx", document.mx());
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    int getMPriceConv = (int) genericDoc.getPropertyLong("mPrice");
+    int mPriceConv = (int) genericDoc.getPropertyLong("price");
+    boolean isSoldConv = genericDoc.getPropertyBoolean("sold");
+    int mxConv = (int) genericDoc.getPropertyLong("mx");
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.setMPrice(getMPriceConv);
+    document.mPrice = mPriceConv;
+    document.setSold(isSoldConv);
+    document.setMx(mxConv);
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNestedDocumentsIndexing.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNestedDocumentsIndexing.JAVA
index b24a19b..3757953 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNestedDocumentsIndexing.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNestedDocumentsIndexing.JAVA
@@ -52,7 +52,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     List<Class<?>> classSet = new ArrayList<Class<?>>();
     classSet.add(GiftContent.class);
     return classSet;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOneBadConstructor.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOneBadConstructor.JAVA
index f3cca50..3939652 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOneBadConstructor.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOneBadConstructor.JAVA
@@ -27,7 +27,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOverloadedGetterIsOk.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOverloadedGetterIsOk.JAVA
new file mode 100644
index 0000000..b7bd374
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOverloadedGetterIsOk.JAVA
@@ -0,0 +1,57 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    builder.setPropertyLong("price", document.getPrice());
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    int getPriceConv = (int) genericDoc.getPropertyLong("price");
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.setPrice(getPriceConv);
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphism.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphism.JAVA
new file mode 100644
index 0000000..5a03bb6
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphism.JAVA
@@ -0,0 +1,103 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent1.SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent2.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("note2")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("sender")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("note1")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    List<Class<?>> classSet = new ArrayList<Class<?>>();
+    classSet.add(Parent1.class);
+    classSet.add(Parent2.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String note2Copy = document.note2;
+    if (note2Copy != null) {
+      builder.setPropertyString("note2", note2Copy);
+    }
+    String senderCopy = document.sender;
+    if (senderCopy != null) {
+      builder.setPropertyString("sender", senderCopy);
+    }
+    String note1Copy = document.note1;
+    if (note1Copy != null) {
+      builder.setPropertyString("note1", note1Copy);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    String[] note2Copy = genericDoc.getPropertyStringArray("note2");
+    String note2Conv = null;
+    if (note2Copy != null && note2Copy.length != 0) {
+      note2Conv = note2Copy[0];
+    }
+    String[] senderCopy = genericDoc.getPropertyStringArray("sender");
+    String senderConv = null;
+    if (senderCopy != null && senderCopy.length != 0) {
+      senderConv = senderCopy[0];
+    }
+    String[] note1Copy = genericDoc.getPropertyStringArray("note1");
+    String note1Conv = null;
+    if (note1Copy != null && note1Copy.length != 0) {
+      note1Conv = note1Copy[0];
+    }
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.note2 = note2Conv;
+    document.sender = senderConv;
+    document.note1 = note1Conv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismDuplicatedParents.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismDuplicatedParents.JAVA
new file mode 100644
index 0000000..5a03bb6
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismDuplicatedParents.JAVA
@@ -0,0 +1,103 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent1.SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent2.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("note2")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("sender")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("note1")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    List<Class<?>> classSet = new ArrayList<Class<?>>();
+    classSet.add(Parent1.class);
+    classSet.add(Parent2.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String note2Copy = document.note2;
+    if (note2Copy != null) {
+      builder.setPropertyString("note2", note2Copy);
+    }
+    String senderCopy = document.sender;
+    if (senderCopy != null) {
+      builder.setPropertyString("sender", senderCopy);
+    }
+    String note1Copy = document.note1;
+    if (note1Copy != null) {
+      builder.setPropertyString("note1", note1Copy);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    String[] note2Copy = genericDoc.getPropertyStringArray("note2");
+    String note2Conv = null;
+    if (note2Copy != null && note2Copy.length != 0) {
+      note2Conv = note2Copy[0];
+    }
+    String[] senderCopy = genericDoc.getPropertyStringArray("sender");
+    String senderConv = null;
+    if (senderCopy != null && senderCopy.length != 0) {
+      senderConv = senderCopy[0];
+    }
+    String[] note1Copy = genericDoc.getPropertyStringArray("note1");
+    String note1Conv = null;
+    if (note1Copy != null && note1Copy.length != 0) {
+      note1Conv = note1Copy[0];
+    }
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.note2 = note2Conv;
+    document.sender = senderConv;
+    document.note1 = note1Conv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismOverrideExtendedProperty.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismOverrideExtendedProperty.JAVA
new file mode 100644
index 0000000..9d52d75
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismOverrideExtendedProperty.JAVA
@@ -0,0 +1,103 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent1.SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent2.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("note2")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("sender")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("note1")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    List<Class<?>> classSet = new ArrayList<Class<?>>();
+    classSet.add(Parent1.class);
+    classSet.add(Parent2.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String note2Copy = document.note2;
+    if (note2Copy != null) {
+      builder.setPropertyString("note2", note2Copy);
+    }
+    String senderCopy = document.sender;
+    if (senderCopy != null) {
+      builder.setPropertyString("sender", senderCopy);
+    }
+    String note1Copy = document.note1;
+    if (note1Copy != null) {
+      builder.setPropertyString("note1", note1Copy);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    String[] note2Copy = genericDoc.getPropertyStringArray("note2");
+    String note2Conv = null;
+    if (note2Copy != null && note2Copy.length != 0) {
+      note2Conv = note2Copy[0];
+    }
+    String[] senderCopy = genericDoc.getPropertyStringArray("sender");
+    String senderConv = null;
+    if (senderCopy != null && senderCopy.length != 0) {
+      senderConv = senderCopy[0];
+    }
+    String[] note1Copy = genericDoc.getPropertyStringArray("note1");
+    String note1Conv = null;
+    if (note1Copy != null && note1Copy.length != 0) {
+      note1Conv = note1Copy[0];
+    }
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.note2 = note2Conv;
+    document.sender = senderConv;
+    document.note1 = note1Conv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismWithNestedType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismWithNestedType.JAVA
new file mode 100644
index 0000000..cbe2ec5
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismWithNestedType.JAVA
@@ -0,0 +1,119 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent1.SCHEMA_NAME)
+          .addParentType($$__AppSearch__Parent2.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("note2")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("sender")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("note1")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("innerContent", $$__AppSearch__Inner.SCHEMA_NAME)
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setShouldIndexNestedProperties(false)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    List<Class<?>> classSet = new ArrayList<Class<?>>();
+    classSet.add(Parent1.class);
+    classSet.add(Parent2.class);
+    classSet.add(Inner.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String note2Copy = document.note2;
+    if (note2Copy != null) {
+      builder.setPropertyString("note2", note2Copy);
+    }
+    String senderCopy = document.sender;
+    if (senderCopy != null) {
+      builder.setPropertyString("sender", senderCopy);
+    }
+    String note1Copy = document.note1;
+    if (note1Copy != null) {
+      builder.setPropertyString("note1", note1Copy);
+    }
+    Inner innerContentCopy = document.innerContent;
+    if (innerContentCopy != null) {
+      GenericDocument innerContentConv = GenericDocument.fromDocumentClass(innerContentCopy);
+      builder.setPropertyDocument("innerContent", innerContentConv);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    String[] note2Copy = genericDoc.getPropertyStringArray("note2");
+    String note2Conv = null;
+    if (note2Copy != null && note2Copy.length != 0) {
+      note2Conv = note2Copy[0];
+    }
+    String[] senderCopy = genericDoc.getPropertyStringArray("sender");
+    String senderConv = null;
+    if (senderCopy != null && senderCopy.length != 0) {
+      senderConv = senderCopy[0];
+    }
+    String[] note1Copy = genericDoc.getPropertyStringArray("note1");
+    String note1Conv = null;
+    if (note1Copy != null && note1Copy.length != 0) {
+      note1Conv = note1Copy[0];
+    }
+    GenericDocument innerContentCopy = genericDoc.getPropertyDocument("innerContent");
+    Inner innerContentConv = null;
+    if (innerContentCopy != null) {
+      innerContentConv = innerContentCopy.toDocumentClass(Inner.class);
+    }
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.note2 = note2Conv;
+    document.sender = senderConv;
+    document.note1 = note1Conv;
+    document.innerContent = innerContentConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA
index 2a730d5..91c548b9 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA
@@ -33,7 +33,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA
index 44d3db6..73793bc 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA
@@ -31,7 +31,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_isGetterForBoolean.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_isGetterForBoolean.JAVA
index a676f5c..5ec8f0f 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_isGetterForBoolean.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_isGetterForBoolean.JAVA
@@ -30,7 +30,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA
index 89b7dbb..61d0062 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA
@@ -47,7 +47,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingBoth.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingBoth.JAVA
new file mode 100644
index 0000000..f00cbad
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingBoth.JAVA
@@ -0,0 +1,64 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price1")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price2")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    builder.setPropertyLong("price1", document.getPrice());
+    builder.setPropertyLong("price2", document.price);
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    int getPriceConv = (int) genericDoc.getPropertyLong("price1");
+    int priceConv = (int) genericDoc.getPropertyLong("price2");
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.setPrice(getPriceConv);
+    document.price = priceConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingBothWithDifferentType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingBothWithDifferentType.JAVA
new file mode 100644
index 0000000..c859b06
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingBothWithDifferentType.JAVA
@@ -0,0 +1,63 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.DoublePropertyConfig.Builder("price1")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .build())
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price2")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    builder.setPropertyDouble("price1", document.getPrice());
+    builder.setPropertyLong("price2", document.price);
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    double getPriceConv = genericDoc.getPropertyDouble("price1");
+    int priceConv = (int) genericDoc.getPropertyLong("price2");
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.setPrice(getPriceConv);
+    document.price = priceConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingField.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingField.JAVA
new file mode 100644
index 0000000..c7d0385
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingField.JAVA
@@ -0,0 +1,57 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    builder.setPropertyLong("price", document.price);
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    int priceConv = (int) genericDoc.getPropertyLong("price");
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.price = priceConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingGetter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingGetter.JAVA
new file mode 100644
index 0000000..b7bd374
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingGetter.JAVA
@@ -0,0 +1,57 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Gift implements DocumentClassFactory<Gift> {
+  public static final String SCHEMA_NAME = "Gift";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.LongPropertyConfig.Builder("price")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setIndexingType(AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
+    GenericDocument.Builder<?> builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    builder.setPropertyLong("price", document.getPrice());
+    return builder.build();
+  }
+
+  @Override
+  public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
+    String idConv = genericDoc.getId();
+    String namespaceConv = genericDoc.getNamespace();
+    int getPriceConv = (int) genericDoc.getPropertyLong("price");
+    Gift document = new Gift();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.setPrice(getPriceConv);
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringPropertyJoinableType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringPropertyJoinableType.JAVA
index 722ebf59..85c7a4e 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringPropertyJoinableType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringPropertyJoinableType.JAVA
@@ -33,7 +33,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA
index c30c660..93ec189 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA
@@ -37,7 +37,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass.JAVA
index 9694ee2..3d0e5c3 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass.JAVA
@@ -42,7 +42,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassPojoAncestor.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassPojoAncestor.JAVA
index 8f600f6..e33830e 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassPojoAncestor.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassPojoAncestor.JAVA
@@ -42,7 +42,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassWithPrivateFields.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassWithPrivateFields.JAVA
index d7feadb..a49c86c 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassWithPrivateFields.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassWithPrivateFields.JAVA
@@ -45,7 +45,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_changeSchemaName.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_changeSchemaName.JAVA
index 08e306c..a68d848 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_changeSchemaName.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_changeSchemaName.JAVA
@@ -39,7 +39,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_multipleChangedSchemaNames.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_multipleChangedSchemaNames.JAVA
index ac5dcb2..766c7d5 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_multipleChangedSchemaNames.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_multipleChangedSchemaNames.JAVA
@@ -42,7 +42,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA
index 0643cf9..1f34209 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA
@@ -162,7 +162,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     List<Class<?>> classSet = new ArrayList<Class<?>>();
     classSet.add(Gift.class);
     return classSet;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
index 15e72e7..68ef053 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
@@ -99,7 +99,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleConventions.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleConventions.JAVA
index cea4e5d..b746c00 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleConventions.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleConventions.JAVA
@@ -43,7 +43,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleSetters.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleSetters.JAVA
index 44d3db6..73793bc 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleSetters.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleSetters.JAVA
@@ -31,7 +31,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_usableFactoryMethod_unusableConstructor.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_usableFactoryMethod_unusableConstructor.JAVA
index e356243..eb55ad1 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_usableFactoryMethod_unusableConstructor.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_usableFactoryMethod_unusableConstructor.JAVA
@@ -31,7 +31,7 @@
   }
 
   @Override
-  public List<Class<?>> getNestedDocumentClasses() throws AppSearchException {
+  public List<Class<?>> getDependencyDocumentClasses() throws AppSearchException {
     return Collections.emptyList();
   }
 
diff --git a/arch/OWNERS b/arch/OWNERS
index 70bfefa..f3a37eb 100644
--- a/arch/OWNERS
+++ b/arch/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 181715
 sergeyv@google.com
 yboyar@google.com
 sanura@google.com
\ No newline at end of file
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
index 4103bf2..4573d0f 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
@@ -233,8 +233,11 @@
         // calling `generateReleaseBaselineProfiles`. On Agp 8.1 instead, it works as intended and
         // we can merge all the variants with `mergeIntoMain` true, independently from the build
         // type.
-        data class TaskAndFolderName(val taskVariantName: String, val folderVariantName: String)
-        val (mergeAwareVariantName, mergeAwareVariantOutput) = if (mergeIntoMain) {
+        data class TaskAndFolderName(
+            val taskVariantName: String,
+            val folderVariantName: String
+        )
+        val (mergeAwareTaskName, mergeAwareVariantOutput) = if (mergeIntoMain) {
             if (supportsFeature(AgpFeature.TEST_MODULE_SUPPORTS_MULTIPLE_BUILD_TYPES)) {
                 TaskAndFolderName(
                     taskVariantName = "",
@@ -265,12 +268,18 @@
 
         val mergeTaskProvider = MergeBaselineProfileTask.maybeRegisterForMerge(
             project = project,
-            variantName = mergeAwareVariantName,
+            variantName = variant.name,
+            mergeAwareTaskName = mergeAwareTaskName,
             hasDependencies = baselineProfileConfiguration.allDependencies.isNotEmpty(),
             sourceProfilesFileCollection = baselineProfileConfiguration,
             outputDir = mergedTaskOutputDir,
             filterRules = variantConfiguration.filterRules,
-            library = isLibraryModule()
+            library = isLibraryModule(),
+
+            // Note that the merge task is the last task only if saveInSrc is disabled. When
+            // saveInSrc is enabled an additional task is created to copy the profile in the sources
+            // folder.
+            isLastTask = !variantConfiguration.saveInSrc
         )
 
         // If `saveInSrc` is true, we create an additional task to copy the output
@@ -292,10 +301,12 @@
             // already handled in the MergeBaselineProfileTask.
             val copyTaskProvider = MergeBaselineProfileTask.maybeRegisterForCopy(
                 project = project,
-                variantName = mergeAwareVariantName,
+                variantName = variant.name,
+                mergeAwareTaskName = mergeAwareTaskName,
                 library = isLibraryModule(),
                 sourceDir = mergeTaskProvider.flatMap { it.baselineProfileDir },
                 outputDir = project.provider { srcOutputDir },
+                isLastTask = true
             )
 
             // Applies the source path for this variant
@@ -425,7 +436,7 @@
         // depending on the configuration.
         maybeCreateGenerateTask<Task>(
             project = project,
-            variantName = mergeAwareVariantName,
+            variantName = mergeAwareTaskName,
             lastTaskProvider = lastTaskProvider
         )
 
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
index c5b75e2..3ff0f4b 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
@@ -69,15 +69,17 @@
         internal fun maybeRegisterForMerge(
             project: Project,
             variantName: String,
+            mergeAwareTaskName: String,
             hasDependencies: Boolean = false,
             library: Boolean,
             sourceProfilesFileCollection: FileCollection,
             outputDir: Provider<Directory>,
             filterRules: List<Pair<RuleType, String>> = listOf(),
+            isLastTask: Boolean
         ): TaskProvider<MergeBaselineProfileTask> {
             return project
                 .tasks
-                .maybeRegister(MERGE_TASK_NAME, variantName, TASK_NAME_SUFFIX) { task ->
+                .maybeRegister(MERGE_TASK_NAME, mergeAwareTaskName, TASK_NAME_SUFFIX) { task ->
 
                     // Sets whether or not baseline profile dependencies have been set.
                     // If they haven't, the task will fail at execution time.
@@ -103,32 +105,49 @@
                     // Sets whether this task has been configured for a library. In this case,
                     // startup profiles are not handled.
                     task.library.set(library)
+
+                    // Determines whether this is the last task to be executed. This flag is used
+                    // exclusively for logging purposes.
+                    task.lastTask.set(isLastTask)
                 }
         }
 
         internal fun maybeRegisterForCopy(
             project: Project,
             variantName: String,
+            mergeAwareTaskName: String,
             library: Boolean,
             sourceDir: Provider<Directory>,
             outputDir: Provider<Directory>,
+            isLastTask: Boolean
         ): TaskProvider<MergeBaselineProfileTask> {
             return project
                 .tasks
-                .maybeRegister(COPY_TASK_NAME, variantName, "baselineProfileIntoSrc") { task ->
+                .maybeRegister(
+                    COPY_TASK_NAME,
+                    mergeAwareTaskName,
+                    "baselineProfileIntoSrc"
+                ) { task ->
                     task.baselineProfileFileCollection.from.add(sourceDir)
                     task.baselineProfileDir.set(outputDir)
                     task.library.set(library)
+                    task.variantName.set(variantName)
+
+                    // Determines whether this is the last task to be executed. This flag is used
+                    // exclusively for logging purposes.
+                    task.lastTask.set(isLastTask)
                 }
         }
     }
 
-    @get: Input
-    @get: Optional
+    @get:Input
     abstract val variantName: Property<String>
 
-    @get: Input
-    @get: Optional
+    @get:Input
+    abstract val lastTask: Property<Boolean>
+
+    @get:Input
+    @get:Optional
     abstract val hasDependencies: Property<Boolean>
 
     @get: Input
@@ -236,6 +255,14 @@
                 delete()
                 if (filteredProfileRules.isNotEmpty()) {
                     writeText(filteredProfileRules.joinToString(System.lineSeparator()))
+                    if (lastTask.get()) {
+                        logger.warn(
+                            """
+                            A baseline profile was generated for the variant `${variantName.get()}`:
+                            $absolutePath
+                        """.trimIndent()
+                        )
+                    }
                 }
             }
 
@@ -278,6 +305,14 @@
                 delete()
                 if (sortedProfileRules.isNotEmpty()) {
                     writeText(sortedProfileRules.joinToString(System.lineSeparator()))
+                    if (lastTask.get()) {
+                        logger.warn(
+                            """
+                            A startup profile was generated for the variant `${variantName.get()}`:
+                            $absolutePath
+                        """.trimIndent()
+                        )
+                    }
                 }
             }
     }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
index c015ad6..4fbe5ec 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -96,9 +96,13 @@
             )
         )
 
-        gradleRunner
-            .withArguments("generateBaselineProfile", "--stacktrace")
-            .build()
+        gradleRunner.build("generateBaselineProfile") {
+            val notFound = it.lines().requireInOrder(
+                "A baseline profile was generated for the variant `release`:",
+                baselineProfileFile("main").absolutePath
+            )
+            assertThat(notFound).isEmpty()
+        }
 
         assertThat(readBaselineProfileFileContent("main"))
             .containsExactly(
@@ -131,9 +135,15 @@
             )
         )
 
-        gradleRunner
-            .withArguments("generateBaselineProfile", "--stacktrace")
-            .build()
+        gradleRunner.build("generateBaselineProfile") {
+            val notFound = it.lines().requireInOrder(
+                "A baseline profile was generated for the variant `release`:",
+                baselineProfileFile("release").absolutePath,
+                "A startup profile was generated for the variant `release`:",
+                startupProfileFile("release").absolutePath
+            )
+            assertThat(notFound).isEmpty()
+        }
 
         assertThat(readBaselineProfileFileContent("release"))
             .containsExactly(
@@ -197,9 +207,18 @@
             contains("generatePaidReleaseBaselineProfile - ")
         }
 
-        gradleRunner
-            .withArguments("generateReleaseBaselineProfile", "--stacktrace")
-            .build()
+        gradleRunner.build("generateReleaseBaselineProfile") {
+            val expected = arrayOf("freeRelease", "paidRelease").flatMap { variantName ->
+                listOf(
+                    "A baseline profile was generated for the variant `$variantName`:",
+                    baselineProfileFile(variantName).absolutePath,
+                    "A startup profile was generated for the variant `$variantName`:",
+                    startupProfileFile(variantName).absolutePath
+                )
+            }
+            val notFound = it.lines().requireInOrder(*expected.toTypedArray())
+            assertThat(notFound).isEmpty()
+        }
 
         assertThat(readBaselineProfileFileContent("freeRelease"))
             .containsExactly(
@@ -604,10 +623,18 @@
         }
 
         // Asserts that the profile is not generated in the src folder
-        gradleRunner.build("generateFreeReleaseBaselineProfile") {}
+        gradleRunner.build("generateFreeReleaseBaselineProfile") {
 
-        val profileFile = baselineProfileFile("freeRelease")
-        assertThat(profileFile.exists()).isFalse()
+            // Note that here the profiles are generated in the intermediates so the output does
+            // not matter.
+            val notFound = it.lines().requireInOrder(
+                "A baseline profile was generated for the variant `freeRelease`:",
+                "A startup profile was generated for the variant `freeRelease`:",
+            )
+            assertThat(notFound).isEmpty()
+        }
+
+        assertThat(baselineProfileFile("freeRelease").exists()).isFalse()
     }
 
     @Test
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
index bd543a3..9782080 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
@@ -79,7 +79,8 @@
             simplifiedTimingOnlyMode = true,
             profiler = null,
             warmupCount = 100,
-            measurementCount = 1000
+            measurementCount = 1000,
+            cpuEventCountersMask = 0x0,
         ),
         expectedWarmups = 0,
         expectedMeasurements = 1,
@@ -94,7 +95,8 @@
             simplifiedTimingOnlyMode = true,
             profiler = null,
             warmupCount = 100,
-            measurementCount = 1000
+            measurementCount = 1000,
+            cpuEventCountersMask = 0x0,
         ),
         expectedWarmups = 0,
         expectedMeasurements = 10,
@@ -110,7 +112,8 @@
             simplifiedTimingOnlyMode = false,
             profiler = null,
             warmupCount = null,
-            measurementCount = null
+            measurementCount = null,
+            cpuEventCountersMask = 0x0,
         ),
         expectedWarmups = null,
         expectedMeasurements = 55, // includes allocations
@@ -125,7 +128,8 @@
             simplifiedTimingOnlyMode = false,
             profiler = null,
             warmupCount = 10,
-            measurementCount = 100
+            measurementCount = 100,
+            cpuEventCountersMask = 0x0,
         ),
         expectedWarmups = 10,
         expectedMeasurements = 105, // includes allocations
@@ -140,7 +144,8 @@
             simplifiedTimingOnlyMode = false,
             profiler = MethodTracing,
             warmupCount = 5,
-            measurementCount = 10
+            measurementCount = 10,
+            cpuEventCountersMask = 0x0,
         ),
         expectedWarmups = 5,
         expectedMeasurements = 15, // profiler not measured, not accounted for here
@@ -156,7 +161,8 @@
             simplifiedTimingOnlyMode = true,
             profiler = MethodTracing,
             warmupCount = 100,
-            measurementCount = 10
+            measurementCount = 10,
+            cpuEventCountersMask = 0x0,
         ),
         expectedWarmups = 100,
         expectedMeasurements = 10,
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
index 372463f..393f8df 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
@@ -212,8 +212,8 @@
         assertTrue("$displayStringV1 should contain foo", displayStringV1.contains("foo"))
         assertTrue("$displayStringV2 should contain foo", displayStringV2.contains("foo"))
         assertTrue(
-            "$displayStringV2 should contain [trace](file://bar)",
-            displayStringV2.contains("[trace](file://bar)")
+            "$displayStringV2 should contain [Trace](file://bar)",
+            displayStringV2.contains("[Trace](file://bar)")
         )
 
         // check attribute presence and naming
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/CpuCounterTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/CpuEventCounterTest.kt
similarity index 67%
rename from benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/CpuCounterTest.kt
rename to benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/CpuEventCounterTest.kt
index 61f29ec..5a36760 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/CpuCounterTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/CpuEventCounterTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.benchmark
 
-import android.os.Build
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
@@ -32,35 +31,18 @@
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = 21)
-class CpuCounterTest {
-    private val perfHardenProp = PropOverride("security.perf_harden", "0")
-    private var shouldResetEnforce1 = false
-
+class CpuEventCounterTest {
     @Before
     fun before() {
-        // TODO: make this automatic
-        if (Build.VERSION.SDK_INT > 29) {
-            val blockedBySelinux = Shell.isSELinuxEnforced()
-            assumeTrue(
-                "blocked by selinux = $blockedBySelinux, rooted = ${DeviceInfo.isRooted}",
-                !blockedBySelinux || DeviceInfo.isRooted
-            )
-            if (blockedBySelinux && DeviceInfo.isRooted) {
-                Shell.executeScriptSilent("setenforce 0")
-                shouldResetEnforce1 = true
-            }
-            perfHardenProp.forceValue()
+        // skip test if need root, or event fails to enable
+        CpuEventCounter.forceEnable()?.let { errorMessage ->
+            assumeTrue(errorMessage, false)
         }
-        val error = CpuCounter.checkPerfEventSupport()
-        assumeTrue(error, error == null)
     }
 
     @After
     fun after() {
-        perfHardenProp.resetIfOverridden()
-        if (shouldResetEnforce1) {
-            Shell.executeScriptSilent("setenforce 1")
-        }
+        CpuEventCounter.reset()
     }
 
     /**
@@ -70,14 +52,14 @@
      * instructions != cycles != l1 misses), since this may be brittle.
      */
     @Test
-    fun basic() = CpuCounter().use { counter ->
-        val values = CpuCounter.Values()
+    fun basic() = CpuEventCounter().use { counter ->
+        val values = CpuEventCounter.Values()
 
         counter.resetEvents(
             listOf(
-                CpuCounter.Event.Instructions,
-                CpuCounter.Event.CpuCycles,
-                CpuCounter.Event.L1IReferences,
+                CpuEventCounter.Event.Instructions,
+                CpuEventCounter.Event.CpuCycles,
+                CpuEventCounter.Event.L1IReferences,
             )
         )
         counter.reset()
@@ -110,25 +92,25 @@
 
         // As counters are enabled in order of ID, these are in order of ID as well
         if (values.numberOfCounters >= 1) {
-            assertNotEquals(0, values.getValue(CpuCounter.Event.Instructions))
+            assertNotEquals(0, values.getValue(CpuEventCounter.Event.Instructions))
         }
         if (values.numberOfCounters >= 2) {
-            assertNotEquals(0, values.getValue(CpuCounter.Event.CpuCycles))
+            assertNotEquals(0, values.getValue(CpuEventCounter.Event.CpuCycles))
         }
         if (values.numberOfCounters >= 3) {
-            assertNotEquals(0, values.getValue(CpuCounter.Event.L1IMisses))
+            assertNotEquals(0, values.getValue(CpuEventCounter.Event.L1IMisses))
         }
     }
 
     @Test
-    fun instructions() = CpuCounter().use { counter ->
-        val values = CpuCounter.Values()
+    fun instructions() = CpuEventCounter().use { counter ->
+        val values = CpuEventCounter.Values()
 
         counter.resetEvents(
             listOf(
-                CpuCounter.Event.Instructions,
-                CpuCounter.Event.CpuCycles,
-                CpuCounter.Event.L1IReferences
+                CpuEventCounter.Event.Instructions,
+                CpuEventCounter.Event.CpuCycles,
+                CpuEventCounter.Event.L1IReferences
             )
         )
 
@@ -144,7 +126,7 @@
             }
             counter.stop()
             counter.read(values)
-            values.getValue(CpuCounter.Event.Instructions)
+            values.getValue(CpuEventCounter.Event.Instructions)
         }
 
         assertTrue(instructions.all { it != 0L })
@@ -157,8 +139,8 @@
     }
 
     @Test
-    fun read_withoutReset(): Unit = CpuCounter().use { counter ->
-        val values = CpuCounter.Values()
+    fun read_withoutReset(): Unit = CpuEventCounter().use { counter ->
+        val values = CpuEventCounter.Values()
 
         // not yet reset, should fail...
         assertFailsWith<IllegalStateException> {
@@ -169,10 +151,10 @@
     }
 
     @Test
-    fun read_afterClose(): Unit = CpuCounter().use { counter ->
-        val values = CpuCounter.Values()
+    fun read_afterClose(): Unit = CpuEventCounter().use { counter ->
+        val values = CpuEventCounter.Values()
         // reset, but closed / deleted, should fail...
-        counter.resetEvents(listOf(CpuCounter.Event.Instructions))
+        counter.resetEvents(listOf(CpuEventCounter.Event.Instructions))
         counter.close()
         assertFailsWith<IllegalStateException> {
             counter.read(values)
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/InstrumentationResultsTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/InstrumentationResultsTest.kt
index b785d18..809da0e 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/InstrumentationResultsTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/InstrumentationResultsTest.kt
@@ -18,30 +18,29 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import java.io.File
+import kotlin.test.assertFailsWith
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-public class InstrumentationResultsTest {
+class InstrumentationResultsTest {
     @Test
-    public fun ideSummary_alignment() {
-        val summary1 = InstrumentationResults.ideSummaryLine(
-            key = "foo",
+    fun ideSummaryBasicMicro_alignment() {
+        val summary1 = InstrumentationResults.ideSummaryBasicMicro(
+            benchmarkName = "foo",
             nanos = 1000.0,
             allocations = 100.0,
-            traceRelPath = "path",
-            profilerResult = null
+            profilerResults = emptyList()
         )
-        val summary2 = InstrumentationResults.ideSummaryLine(
-            key = "fooBarLongerKey",
+        val summary2 = InstrumentationResults.ideSummaryBasicMicro(
+            benchmarkName = "fooBarLongerKey",
             nanos = 10000.0,
             allocations = 0.0,
-            traceRelPath = "path",
-            profilerResult = null
+            profilerResults = emptyList()
         )
-
         assertEquals(
             summary1.indexOf("foo"),
             summary2.indexOf("foo")
@@ -49,60 +48,237 @@
     }
 
     @Test
-    public fun ideSummary_allocs() {
+    fun ideSummaryBasicMicro_allocs() {
         assertEquals(
             "        1,000   ns    foo",
-            InstrumentationResults.ideSummaryLine("foo", 1000.0, null, null, null)
+            InstrumentationResults.ideSummaryBasicMicro("foo", 1000.0, null, emptyList())
         )
         assertEquals(
             "        1,000   ns          10 allocs    foo",
-            InstrumentationResults.ideSummaryLine("foo", 1000.0, 10.0, null, null)
+            InstrumentationResults.ideSummaryBasicMicro("foo", 1000.0, 10.0, emptyList())
         )
     }
 
     @Test
-    public fun ideSummary_decimal() {
+    fun ideSummaryBasicMicro_decimal() {
         assertEquals(
             "        1,000   ns    foo",
-            InstrumentationResults.ideSummaryLine("foo", 1000.0, null, null, null)
+            InstrumentationResults.ideSummaryBasicMicro("foo", 1000.0, null, emptyList())
         )
         assertEquals(
             "          100   ns    foo", // 10ths not shown ...
-            InstrumentationResults.ideSummaryLine("foo", 100.4, null, null, null)
+            InstrumentationResults.ideSummaryBasicMicro("foo", 100.4, null, emptyList())
         )
         assertEquals(
             "           99.9 ns    foo", // ... until value is < 100
-            InstrumentationResults.ideSummaryLine("foo", 99.9, null, null, null)
+            InstrumentationResults.ideSummaryBasicMicro("foo", 99.9, null, emptyList())
         )
         assertEquals(
             "            1.0 ns    foo",
-            InstrumentationResults.ideSummaryLine("foo", 1.0, null, null, null)
+            InstrumentationResults.ideSummaryBasicMicro("foo", 1.0, null, emptyList())
         )
     }
 
     @Test
-    public fun ideSummary_traceRelPath() {
-        assertEquals(
-            "        1,000   ns    [trace](file://bar)    foo",
-            InstrumentationResults.ideSummaryLine("foo", 1000.0, null, "bar", null)
-        )
-    }
-
-    @Test
-    public fun ideSummary_profilerResult() {
+    fun ideSummaryBasicMicro_profilerResult() {
         assertEquals(
             "        1,000   ns    [Trace Label](file://tracePath.trace)    foo",
-            InstrumentationResults.ideSummaryLine(
-                key = "foo",
+            InstrumentationResults.ideSummaryBasicMicro(
+                benchmarkName = "foo",
                 nanos = 1000.0,
                 allocations = null,
-                traceRelPath = null,
-                profilerResult = Profiler.ResultFile(
+                listOf(Profiler.ResultFile(
                     label = "Trace Label",
                     outputRelativePath = "tracePath.trace",
                     source = MethodTracing
-                )
+                ))
             )
         )
     }
+
+    private fun createAbsoluteTracePaths(
+        @Suppress("SameParameterValue") count: Int
+    ) = List(count) {
+        File(Outputs.dirUsableByAppAndShell, "iter$it.trace").absolutePath
+    }
+
+    @Test
+    fun ideSummary_singleMinimal() {
+        val metricResult = MetricResult("Metric", listOf(0.0, 1.1, 2.2))
+
+        assertEquals(0, metricResult.minIndex)
+        assertEquals(1, metricResult.medianIndex)
+        assertEquals(2, metricResult.maxIndex)
+        val absoluteTracePaths = createAbsoluteTracePaths(3)
+        val summary = InstrumentationResults.ideSummary(
+            testName = "foo",
+            measurements = BenchmarkResult.Measurements(
+                singleMetrics = listOf(metricResult),
+                sampledMetrics = emptyList()
+            ),
+            iterationTracePaths = absoluteTracePaths
+        )
+        assertEquals(
+            """
+                |foo
+                |  Metric   min 0.0,   median 1.1,   max 2.2
+                |
+            """.trimMargin(),
+            summary.summaryV1
+        )
+        assertEquals(
+            """
+                |foo
+                |  Metric   [min 0.0](file://iter0.trace),   [median 1.1](file://iter1.trace),   [max 2.2](file://iter2.trace)
+                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
+                |
+            """.trimMargin(),
+            summary.summaryV2
+        )
+    }
+
+    @Test
+    fun ideSummary_singleComplex() {
+        val metric1 = MetricResult("Metric1", listOf(0.0, 1.0, 2.0))
+        val metric2 = MetricResult("Metric2", listOf(222.0, 111.0, 0.0))
+        val summary = InstrumentationResults.ideSummary(
+            testName = "foo",
+            measurements = BenchmarkResult.Measurements(
+                singleMetrics = listOf(metric1, metric2),
+                sampledMetrics = emptyList()
+            ),
+            iterationTracePaths = createAbsoluteTracePaths(3)
+        )
+        assertEquals(
+            """
+                |foo
+                |  Metric1   min   0.0,   median   1.0,   max   2.0
+                |  Metric2   min   0.0,   median 111.0,   max 222.0
+                |
+            """.trimMargin(),
+            summary.summaryV1
+        )
+        assertEquals(
+            """
+                |foo
+                |  Metric1   [min   0.0](file://iter0.trace),   [median   1.0](file://iter1.trace),   [max   2.0](file://iter2.trace)
+                |  Metric2   [min   0.0](file://iter2.trace),   [median 111.0](file://iter1.trace),   [max 222.0](file://iter0.trace)
+                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
+                |
+            """.trimMargin(),
+            summary.summaryV2
+        )
+    }
+
+    @Test
+    fun ideSummary_sampledMinimal() {
+        val metricResult = MetricResult("Metric1", List(101) { it.toDouble() })
+        val summary = InstrumentationResults.ideSummary(
+            testName = "foo",
+            measurements = BenchmarkResult.Measurements(
+                singleMetrics = emptyList(),
+                sampledMetrics = listOf(metricResult)
+            ),
+            iterationTracePaths = createAbsoluteTracePaths(3)
+        )
+        assertEquals(
+            """
+                |foo
+                |  Metric1   P50   50.0,   P90   90.0,   P95   95.0,   P99   99.0
+                |
+            """.trimMargin(),
+            summary.summaryV1
+        )
+        assertEquals(
+            """
+                |foo
+                |  Metric1   P50   50.0,   P90   90.0,   P95   95.0,   P99   99.0
+                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
+                |
+            """.trimMargin(),
+            summary.summaryV2
+        )
+    }
+
+    @Test
+    public fun ideSummary_complex() {
+        val single = MetricResult("Metric1", listOf(0.0, 1.0, 2.0))
+        val sampled = MetricResult("Metric2", List(101) { it.toDouble() })
+        val absoluteTracePaths = createAbsoluteTracePaths(3)
+        val summary = InstrumentationResults.ideSummary(
+            testName = "foo",
+            measurements = BenchmarkResult.Measurements(
+                singleMetrics = listOf(single),
+                sampledMetrics = listOf(sampled)
+            ),
+            iterationTracePaths = absoluteTracePaths
+        )
+        assertEquals(
+            """
+                |foo
+                |  Metric1   min   0.0,   median   1.0,   max   2.0
+                |  Metric2   P50   50.0,   P90   90.0,   P95   95.0,   P99   99.0
+                |
+            """.trimMargin(),
+            summary.summaryV1
+        )
+        assertEquals(
+            """
+                |foo
+                |  Metric1   [min   0.0](file://iter0.trace),   [median   1.0](file://iter1.trace),   [max   2.0](file://iter2.trace)
+                |  Metric2   P50   50.0,   P90   90.0,   P95   95.0,   P99   99.0
+                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
+                |
+            """.trimMargin(),
+            summary.summaryV2
+        )
+    }
+
+    @Test
+    fun ideSummary_warning() {
+        val metricResult = MetricResult("Metric", listOf(0.0, 1.0, 2.0))
+        val absoluteTracePaths = createAbsoluteTracePaths(3)
+        val summary = InstrumentationResults.ideSummary(
+            warningMessage = "warning\nstring",
+            testName = "foo",
+            measurements = BenchmarkResult.Measurements(
+                singleMetrics = listOf(metricResult),
+                sampledMetrics = emptyList()
+            ),
+            iterationTracePaths = absoluteTracePaths
+        )
+        assertEquals(
+            """
+                |warning
+                |string
+                |foo
+                |  Metric   min 0.0,   median 1.0,   max 2.0
+                |
+            """.trimMargin(),
+            summary.summaryV1
+        )
+        assertEquals(
+            """
+                |warning
+                |string
+                |foo
+                |  Metric   [min 0.0](file://iter0.trace),   [median 1.0](file://iter1.trace),   [max 2.0](file://iter2.trace)
+                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
+                |
+            """.trimMargin(),
+            summary.summaryV2
+        )
+    }
+
+    @Test
+    fun ideSummary_requireMeasurementsNotEmpty() {
+        assertFailsWith<IllegalArgumentException> {
+            InstrumentationResults.ideSummary(
+                measurements = BenchmarkResult.Measurements(
+                    singleMetrics = emptyList(),
+                    sampledMetrics = emptyList()
+                ),
+            )
+        }
+    }
 }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
index 8ee8745..50b3f6b 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
@@ -63,7 +63,6 @@
     val enableCompilation: Boolean
     val killProcessDelayMillis: Long
     val enableStartupProfiles: Boolean
-    val strictStartupProfiles: Boolean
     val dryRunMode: Boolean
 
     // internal properties are microbenchmark only
@@ -76,6 +75,8 @@
     internal val profilerSampleFrequency: Int
     internal val profilerSampleDurationSeconds: Long
     internal val thermalThrottleSleepDurationSeconds: Long
+    private val cpuEventCounterEnable: Boolean
+    internal val cpuEventCounterMask: Int
 
     internal var error: String? = null
     internal val additionalTestOutputDir: String?
@@ -180,6 +181,22 @@
             )
         }
 
+        cpuEventCounterEnable =
+            arguments.getBenchmarkArgument("cpuEventCounter.enable")?.toBoolean() ?: false
+        cpuEventCounterMask =
+            if (cpuEventCounterEnable) {
+                arguments.getBenchmarkArgument("cpuEventCounter.events", "Instructions,CpuCycles")
+                    .split(",").map { eventName ->
+                            CpuEventCounter.Event.valueOf(eventName)
+                    }.getFlags()
+            } else {
+                0x0
+            }
+        if (cpuEventCounterEnable && cpuEventCounterMask == 0x0) {
+            error = "Must set a cpu event counters mask to use counters." +
+                " See CpuEventCounters.Event for flag definitions."
+        }
+
         thermalThrottleSleepDurationSeconds =
             arguments.getBenchmarkArgument("thermalThrottle.sleepDurationSeconds")?.ifBlank { null }
                 ?.toLong()
@@ -193,15 +210,12 @@
 
         enableStartupProfiles =
             arguments.getBenchmarkArgument("startupProfiles.enable")?.toBoolean() ?: true
-
-        strictStartupProfiles =
-            arguments.getBenchmarkArgument("startupProfiles.strict")?.toBoolean() ?: false
     }
 
     fun methodTracingEnabled(): Boolean {
         return when {
             dryRunMode -> false
-            _profiler != null && _profiler.javaClass.simpleName == "MethodTracing" -> true
+            _profiler == MethodTracing -> true
             else -> false
         }
     }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
index 46dacdc..152df9f 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -23,7 +23,6 @@
 import androidx.annotation.RestrictTo
 import androidx.annotation.VisibleForTesting
 import androidx.benchmark.Errors.PREFIX
-import androidx.benchmark.InstrumentationResults.ideSummaryLineWrapped
 import androidx.benchmark.InstrumentationResults.instrumentationReport
 import androidx.benchmark.InstrumentationResults.reportBundle
 import java.util.concurrent.TimeUnit
@@ -91,7 +90,8 @@
             profiler = Arguments.profiler,
             warmupCount = warmupCount,
             measurementCount = Arguments.iterations ?: measurementCount,
-            simplifiedTimingOnlyMode = simplifiedTimingOnlyMode
+            simplifiedTimingOnlyMode = simplifiedTimingOnlyMode,
+            cpuEventCountersMask = Arguments.cpuEventCounterMask
         )
     )
 
@@ -204,9 +204,7 @@
      */
     fun pauseTiming() {
         check(!paused) { "Unable to pause the benchmark. The benchmark has already paused." }
-        if (!currentPhase.ignorePauseEvent) {
-            currentMetrics.capturePaused()
-        }
+        currentMetrics.capturePaused()
         paused = true
     }
 
@@ -236,9 +234,7 @@
      */
     fun resumeTiming() {
         check(paused) { "Unable to resume the benchmark. The benchmark is already running." }
-        if (!currentPhase.ignorePauseEvent) {
-            currentMetrics.captureResumed()
-        }
+        currentMetrics.captureResumed()
         paused = false
     }
 
@@ -304,9 +300,9 @@
             if (it.warmupManager != null) {
                 // warmup phase
                 currentMetrics.captureInit()
-                // Note that warmupManager presence implies only one metric captured,
-                // this is validated in MicrobenchmarkPhase init
-                val lastMeasuredWarmupValue = currentMetrics.data.last()[0]
+                // Note that warmup is based on repeat time, *not* the timeNs metric, since we want
+                // to account for paused time during warmup (paused work should stabilize too)
+                val lastMeasuredWarmupValue = currentMetrics.peekSingleRepeatTime()
                 if (it.warmupManager.onNextIteration(lastMeasuredWarmupValue)) {
                     warmupEstimatedIterationTimeNs = lastMeasuredWarmupValue
                     warmupRepeats = currentMeasurement
@@ -491,23 +487,17 @@
             // these 'legacy' CI output metrics are considered output
             metricResults.forEach { it.putInBundle(status, PREFIX) }
         }
-        val nanos = getMinTimeNanos()
-        val allocations = metricResults.firstOrNull { it.name == "allocationCount" }?.median
-        InstrumentationResultScope(status).ideSummaryRecord(
-            summaryV1 = ideSummaryLineWrapped(
-                key = key,
-                nanos = nanos,
-                allocations = allocations,
-                traceRelPath = null,
-                profilerResult = null
+        InstrumentationResultScope(status).reportSummaryToIde(
+            warningMessage = Errors.acquireWarningStringForLogging() ?: "",
+            testName = key,
+            measurements = BenchmarkResult.Measurements(
+                singleMetrics = metricResults,
+                sampledMetrics = emptyList()
             ),
-            summaryV2 = ideSummaryLineWrapped(
-                key = key,
-                nanos = nanos,
-                allocations = allocations,
-                traceRelPath = tracePath?.let { Outputs.relativePathFor(it) },
-                profilerResult = profilerResult
-            ),
+            profilerResults = listOfNotNull(
+                tracePath?.let { Profiler.ResultFile(label = "Trace", absolutePath = tracePath) },
+                profilerResult
+            )
         )
         return status
     }
@@ -605,14 +595,9 @@
                 if (className.isNotEmpty()) "$className.$testName" else testName
 
             instrumentationReport {
-                ideSummaryRecord(
-                    summaryV1 = ideSummaryLineWrapped(
-                        key = fullTestName,
-                        nanos = report.getMetricResult("timeNs").min,
-                        allocations = null,
-                        traceRelPath = null,
-                        profilerResult = null
-                    )
+                reportSummaryToIde(
+                    testName = fullTestName,
+                    measurements = report.metrics,
                 )
             }
 
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuCounter.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt
similarity index 64%
rename from benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuCounter.kt
rename to benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt
index 7e57ab5..df2ba41 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuCounter.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt
@@ -16,6 +16,8 @@
 
 package androidx.benchmark
 
+import android.os.Build
+import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import java.io.Closeable
 
@@ -33,16 +35,17 @@
  *  - security.perf_harden 0
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class CpuCounter : Closeable {
+class CpuEventCounter : Closeable {
     private var profilerPtr = CpuCounterJni.newProfiler()
     private var hasReset = false
 
     fun resetEvents(events: List<Event>) {
+        resetEvents(events.getFlags())
+    }
+
+    fun resetEvents(eventFlags: Int) {
         hasReset = true
-        val flags = events.fold(0) { acc, event ->
-            acc.or(event.flag)
-        }
-        CpuCounterJni.resetEvents(profilerPtr, flags)
+        CpuCounterJni.resetEvents(profilerPtr, eventFlags)
     }
 
     override fun close() {
@@ -103,6 +106,52 @@
 
     companion object {
         fun checkPerfEventSupport(): String? = CpuCounterJni.checkPerfEventSupport()
+
+        /**
+         * Forces system properties and selinux into correct mode for capture
+         *
+         * Reset still required if failure occurs partway through
+         */
+        fun forceEnable(): String? {
+            if (Build.VERSION.SDK_INT >= 29) {
+                Api29Enabler.forceEnable()?.let { return it }
+            }
+            return checkPerfEventSupport()
+        }
+        fun reset() {
+            if (Build.VERSION.SDK_INT >= 29) {
+                Api29Enabler.reset()
+            }
+        }
+
+        /**
+         * Enable setenforce 0 and setprop perf_harden to 0, only observed this required on API 29+
+         */
+        @RequiresApi(29)
+        object Api29Enabler {
+            private val perfHardenProp = PropOverride("security.perf_harden", "0")
+            private var shouldResetEnforce1 = false
+            fun forceEnable(): String? {
+                if (Shell.isSELinuxEnforced()) {
+                    if (DeviceInfo.isRooted) {
+                        Shell.executeScriptSilent("setenforce 0")
+                        shouldResetEnforce1 = true
+                    } else {
+                        return "blocked by selinux, can't `setenforce 0` without rooted device"
+                    }
+                }
+                perfHardenProp.forceValue()
+                return null
+            }
+
+            fun reset() {
+                perfHardenProp.resetIfOverridden()
+                if (shouldResetEnforce1) {
+                    Shell.executeScriptSilent("setenforce 1")
+                    shouldResetEnforce1 = false
+                }
+            }
+        }
     }
 }
 
@@ -121,3 +170,7 @@
     external fun stop(profilerPtr: Long)
     external fun read(profilerPtr: Long, outData: LongArray)
 }
+
+internal fun List<CpuEventCounter.Event>.getFlags() = fold(0) { acc, event ->
+    acc.or(event.flag)
+}
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
index 6ea8155..da6485b 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
@@ -21,13 +21,33 @@
 import androidx.test.platform.app.InstrumentationRegistry
 
 /**
+ * Wrapper for multi studio version link format
+ *
+ * TODO: drop support for very old versions of Studio in Benchmark 1.3,
+ *  and remove v1 protocol support for simplicity (just post v2 twice)
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+data class IdeSummaryPair(
+    val summaryV1: String,
+    val summaryV2: String
+) {
+    constructor(
+        v1lines: List<String>,
+        v2lines: List<String>
+    ) : this(
+        summaryV1 = v1lines.joinToString("\n"),
+        summaryV2 = v2lines.joinToString("\n")
+    )
+}
+
+/**
  * Provides a way to capture all the instrumentation results which needs to be reported.
  * @suppress
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class InstrumentationResultScope(public val bundle: Bundle = Bundle()) {
-    @Suppress("MissingJvmstatic")
-    public fun ideSummaryRecord(
+class InstrumentationResultScope(val bundle: Bundle = Bundle()) {
+
+    private fun reportIdeSummary(
         /**
          * Simple text-only result summary string to output to IDE.
          */
@@ -45,6 +65,28 @@
         bundle.putString(IDE_V2_SUMMARY_KEY, summaryV2)
     }
 
+    fun reportSummaryToIde(
+        warningMessage: String? = null,
+        testName: String? = null,
+        message: String? = null,
+        measurements: BenchmarkResult.Measurements? = null,
+        iterationTracePaths: List<String>? = null,
+        profilerResults: List<Profiler.ResultFile> = emptyList()
+    ) {
+        val summaryPair = InstrumentationResults.ideSummary(
+            warningMessage = warningMessage,
+            testName = testName,
+            message = message,
+            measurements = measurements,
+            iterationTracePaths = iterationTracePaths,
+            profilerResults = profilerResults
+        )
+        reportIdeSummary(
+            summaryV1 = summaryPair.summaryV1,
+            summaryV2 = summaryPair.summaryV2
+        )
+    }
+
     public fun fileRecord(key: String, path: String) {
         bundle.putString("additionalTestOutputFile_$key", path)
     }
@@ -63,20 +105,18 @@
  * @suppress
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public object InstrumentationResults {
-    private const val STUDIO_OUTPUT_KEY_ID = "benchmark"
-
+object InstrumentationResults {
     /**
      * Bundle containing values to be reported at end of run, instead of for each test.
      *
      * See androidx.benchmark.junit.InstrumentationResultsRunListener
      */
-    public val runEndResultBundle: Bundle = Bundle()
+    val runEndResultBundle: Bundle = Bundle()
 
     /**
      * Creates an Instrumentation Result.
      */
-    public fun instrumentationReport(
+    fun instrumentationReport(
         block: InstrumentationResultScope.() -> Unit
     ) {
         val scope = InstrumentationResultScope()
@@ -84,38 +124,161 @@
         reportBundle(scope.bundle)
     }
 
-    // NOTE: this summary line will use default locale to determine separators. As
-    // this line is only meant for human eyes, we don't worry about consistency here.
-    internal fun ideSummaryLine(
-        key: String,
+    /**
+     * Simple single line benchmark output
+     */
+    internal fun ideSummaryBasicMicro(
+        benchmarkName: String,
         nanos: Double,
         allocations: Double?,
-        traceRelPath: String?,
-        profilerResult: Profiler.ResultFile?
+        profilerResults: List<Profiler.ResultFile>,
     ): String {
-        return listOfNotNull(
-            // for readability, report nanos with 10ths only if less than 100
-            if (nanos >= 100.0) {
-                // 13 alignment is enough for ~10 seconds
-                "%,13d   ns".format(nanos.toLong())
-            } else {
-                // 13 + 2(.X) to match alignment above
-                "%,15.1f ns".format(nanos)
-            },
+        // for readability, report nanos with 10ths only if less than 100
+        var output = if (nanos >= 100.0) {
+            // 13 alignment is enough for ~10 seconds
+            "%,13d   ns".format(nanos.toLong())
+        } else {
+            // 13 + 2(.X) to match alignment above
+            "%,15.1f ns".format(nanos)
+        }
+        if (allocations != null) {
             // 9 alignment is enough for ~10 million allocations
-            allocations?.run {
-                "%8d allocs".format(allocations.toInt())
-            },
-            traceRelPath?.run {
-                // always fixed length
-                "[trace](file://$traceRelPath)"
-            },
-            profilerResult?.run {
-                // should be fixed length within a run, as each benchmark will use same profiler
-                "[$label](file://$outputRelativePath)"
-            },
-            key
-        ).joinToString("    ")
+            output += "    %8d allocs".format(allocations.toInt())
+        }
+        profilerResults.forEach {
+            output += "    [${it.label}](file://${it.sanitizedOutputRelativePath})"
+        }
+        output += "    $benchmarkName"
+        return output
+    }
+
+    internal fun ideSummary(
+        warningMessage: String? = null,
+        testName: String? = null,
+        message: String? = null,
+        measurements: BenchmarkResult.Measurements? = null,
+        iterationTracePaths: List<String>? = null,
+        profilerResults: List<Profiler.ResultFile> = emptyList()
+    ): IdeSummaryPair {
+        val v1metricLines: List<String>
+        val v2metricLines: List<String>
+        val linkableIterTraces = iterationTracePaths?.map { absolutePath ->
+            Outputs.relativePathFor(absolutePath)
+                .replace("(", "\\(")
+                .replace(")", "\\)")
+        }
+
+        if (measurements != null) {
+            require(measurements.isNotEmpty()) { "Require non-empty list of metric results." }
+            val setOfMetrics = measurements.singleMetrics.map { it.name }.toSet()
+            // specialized single line codepath for microbenchmarks with only 2 default metrics
+            if (iterationTracePaths == null &&
+                testName != null &&
+                message == null &&
+                measurements.sampledMetrics.isEmpty() &&
+                (setOfMetrics == setOf(
+                    "timeNs",
+                    "allocationCount"
+                ) || setOfMetrics == setOf("timeNs"))
+            ) {
+                val nanos = measurements.singleMetrics.single { it.name == "timeNs" }.min
+                val allocs =
+                    measurements.singleMetrics.singleOrNull { it.name == "allocationCount" }?.min
+                return IdeSummaryPair(
+                    summaryV1 = (warningMessage ?: "") + ideSummaryBasicMicro(
+                        testName,
+                        nanos,
+                        allocs,
+                        emptyList()
+                    ),
+                    summaryV2 = (warningMessage ?: "") + ideSummaryBasicMicro(
+                        testName,
+                        nanos,
+                        allocs,
+                        profilerResults
+                    )
+                )
+            }
+
+            val allMetrics = measurements.singleMetrics + measurements.sampledMetrics
+            val maxLabelLength = allMetrics.maxOf { it.name.length }
+            fun Double.toDisplayString() = "%,.1f".format(this)
+
+            // max string length of any printed min/med/max is the largest max value seen. used to pad.
+            val maxValueLength = allMetrics
+                .maxOf { it.max }
+                .toDisplayString().length
+
+            fun metricLines(
+                singleTransform: (
+                    name: String,
+                    min: String,
+                    median: String,
+                    max: String,
+                    metricResult: MetricResult
+                ) -> String
+            ) = measurements.singleMetrics.map {
+                singleTransform(
+                    it.name.padEnd(maxLabelLength),
+                    it.min.toDisplayString().padStart(maxValueLength),
+                    it.median.toDisplayString().padStart(maxValueLength),
+                    it.max.toDisplayString().padStart(maxValueLength),
+                    it
+                )
+            } + measurements.sampledMetrics.map {
+                val name = it.name.padEnd(maxLabelLength)
+                val p50 = it.p50.toDisplayString().padStart(maxValueLength)
+                val p90 = it.p90.toDisplayString().padStart(maxValueLength)
+                val p95 = it.p95.toDisplayString().padStart(maxValueLength)
+                val p99 = it.p99.toDisplayString().padStart(maxValueLength)
+                // we don't try and link percentiles, since they're grouped across multiple iters
+                "  $name   P50  $p50,   P90  $p90,   P95  $p95,   P99  $p99"
+            }
+
+            v1metricLines = metricLines { name, min, median, max, _ ->
+                "  $name   min $min,   median $median,   max $max"
+            }
+            v2metricLines = if (linkableIterTraces != null) {
+                // Per iteration trace paths present, so link min/med/max to respective iteration traces
+                metricLines { name, min, median, max, result ->
+                    "  $name" +
+                        "   [min $min](file://${linkableIterTraces[result.minIndex]})," +
+                        "   [median $median](file://${linkableIterTraces[result.medianIndex]})," +
+                        "   [max $max](file://${linkableIterTraces[result.maxIndex]})"
+                }
+            } else {
+                // No iteration traces, so just basic list
+                v1metricLines
+            }
+        } else {
+            // no metrics to report
+            v1metricLines = emptyList()
+            v2metricLines = emptyList()
+        }
+
+        val v2traceLinks = if (linkableIterTraces != null) {
+            listOf(
+                "    Traces: Iteration " + linkableIterTraces.mapIndexed { index, path ->
+                    "[$index](file://$path)"
+                }.joinToString(" ")
+            )
+        } else {
+            emptyList()
+        } + profilerResults.map {
+            "    [${it.label}](file://${it.sanitizedOutputRelativePath})"
+        }
+        return IdeSummaryPair(
+            v1lines = listOfNotNull(
+                warningMessage,
+                testName,
+                message,
+            ) + v1metricLines + /* adds \n */ "",
+            v2lines = listOfNotNull(
+                warningMessage,
+                testName,
+                message,
+            ) + v2metricLines + v2traceLinks + /* adds \n */ ""
+        )
     }
 
     /**
@@ -146,29 +309,6 @@
         }
     }
 
-    internal fun ideSummaryLineWrapped(
-        key: String,
-        nanos: Double,
-        allocations: Double?,
-        traceRelPath: String?,
-        profilerResult: Profiler.ResultFile?
-    ): String {
-        val warningLines =
-            Errors.acquireWarningStringForLogging()?.split("\n") ?: listOf()
-        return (warningLines + ideSummaryLine(
-            key = key,
-            nanos = nanos,
-            allocations = allocations,
-            traceRelPath = traceRelPath,
-            profilerResult = profilerResult
-        ))
-            // remove first line if empty
-            .filterIndexed { index, it -> index != 0 || it.isNotBlank() }
-            // join, prepending key to everything but first string,
-            // to make each line look the same
-            .joinToString("\n$STUDIO_OUTPUT_KEY_ID: ")
-    }
-
     /**
      * Report results bundle to instrumentation
      *
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
index ec556be..5ef654e 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
@@ -111,3 +111,48 @@
         currentTotalPaused += Debug.getGlobalAllocCount() - currentPausedStarted
     }
 }
+
+@Suppress
+internal class CpuEventCounterCapture(
+    private val cpuEventCounter: CpuEventCounter,
+    private val events: List<CpuEventCounter.Event>
+) : MetricCapture(events.map { it.name }) {
+    constructor(
+        cpuEventCounter: CpuEventCounter,
+        mask: Int
+    ) : this(cpuEventCounter, CpuEventCounter.Event.values().filter {
+        it.flag.and(mask) != 0
+    })
+
+    private val values = CpuEventCounter.Values()
+    private val flags = events.getFlags()
+    private var hasResetEvents = false
+
+    override fun captureStart(timeNs: Long) {
+        if (!hasResetEvents) {
+            // must be called on measure thread, so we wait until after init (which can be separate)
+            cpuEventCounter.resetEvents(flags)
+            hasResetEvents = true
+        } else {
+            // flags already set, fast path
+            cpuEventCounter.reset()
+        }
+        cpuEventCounter.start()
+    }
+
+    override fun captureStop(timeNs: Long, output: LongArray, offset: Int) {
+        cpuEventCounter.stop()
+        cpuEventCounter.read(values)
+        events.forEachIndexed { index, event ->
+            output[offset + index] = values.getValue(event)
+        }
+    }
+
+    override fun capturePaused() {
+        cpuEventCounter.stop()
+    }
+
+    override fun captureResumed() {
+        cpuEventCounter.start()
+    }
+}
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricsContainer.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricsContainer.kt
index 995ab06..50e30ad 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricsContainer.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricsContainer.kt
@@ -59,7 +59,12 @@
      * These values are used both in metric calculation and trace data, so tracing is extremely low
      * overhead - just the cost of storing the timing data in an additional place in memory.
      */
-    private val traceTiming = LongArray(repeatCount * 2)
+    private val repeatTiming = LongArray(repeatCount * 2)
+
+    fun peekSingleRepeatTime(): Long {
+        check(repeatCount == 1) { "Observed repeat count $repeatCount, expected 1" }
+        return repeatTiming[1] - repeatTiming[0]
+    }
 
     private var runNum: Int = 0
 
@@ -79,7 +84,7 @@
      */
     fun captureStart() {
         val timeNs = System.nanoTime()
-        traceTiming[runNum * 2] = timeNs
+        repeatTiming[runNum * 2] = timeNs
         for (i in metrics.lastIndex downTo 0) {
             metrics[i].captureStart(timeNs) // put the most sensitive metric first to avoid overhead
         }
@@ -97,7 +102,7 @@
             metrics[i].captureStop(timeNs, data[runNum], offset)
             offset += metrics[i].names.size
         }
-        traceTiming[runNum * 2 + 1] = timeNs
+        repeatTiming[runNum * 2 + 1] = timeNs
         runNum += 1
     }
 
@@ -129,9 +134,9 @@
      * Call exactly once at the end of a benchmark.
      */
     fun captureFinished(maxIterations: Int): List<MetricResult> {
-        for (i in 0..traceTiming.lastIndex step 2) {
-            UserspaceTracing.beginSection("measurement ${i / 2}", nanoTime = traceTiming[i])
-            UserspaceTracing.endSection(nanoTime = traceTiming[i + 1])
+        for (i in 0..repeatTiming.lastIndex step 2) {
+            UserspaceTracing.beginSection("measurement ${i / 2}", nanoTime = repeatTiming[i])
+            UserspaceTracing.endSection(nanoTime = repeatTiming[i + 1])
         }
 
         return names.mapIndexed { index, name ->
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt
index fe56b1f..eb6bf1f1 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt
@@ -2,6 +2,7 @@
 
 import android.annotation.SuppressLint
 import android.util.Log
+import androidx.benchmark.CpuEventCounter.Event
 import java.util.concurrent.TimeUnit
 
 internal class MicrobenchmarkPhase(
@@ -13,7 +14,6 @@
 
     val profiler: Profiler? = null,
     val gcBeforePhase: Boolean = false,
-    val ignorePauseEvent: Boolean = false,
     val thermalThrottleSleepsMax: Int = 0,
 ) {
     val metricsContainer = MetricsContainer(metrics, measurementCount)
@@ -21,9 +21,10 @@
     var thermalThrottleSleepSeconds = 0L
 
     init {
-        check(loopMode.warmupManager == null || metricsContainer.names.size == 1) {
-            "If warmup is enabled, must only capture one metric," +
-                " as WarmupManager only one value per repeat"
+        if (loopMode.warmupManager != null) {
+            check(metricsContainer.names.first() == "timeNs" && metricsContainer.names.size <= 2) {
+                "If warmup is enabled, expect to only capture one or two metrics"
+            }
         }
     }
 
@@ -101,6 +102,16 @@
     companion object {
         private val THROTTLE_BACKOFF_S = Arguments.thermalThrottleSleepDurationSeconds
 
+        // static instance ensures there's only one, and we don't leak native memory
+        private val cpuEventCounter: CpuEventCounter by lazy {
+            // As this is only ever enabled by experimental arguments, we force enable this
+            // permanently once the first benchmark uses it, for local runs only.
+            CpuEventCounter.forceEnable()?.let { errorMessage ->
+                throw IllegalStateException(errorMessage)
+            }
+            CpuEventCounter()
+        }
+
         fun dryRunModePhase() = MicrobenchmarkPhase(
             label = "Benchmark DryRun Timing",
             measurementCount = 1,
@@ -114,23 +125,40 @@
         )
 
         fun warmupPhase(
-            warmupManager: WarmupManager
+            warmupManager: WarmupManager,
+            collectCpuEventInstructions: Boolean,
         ) = MicrobenchmarkPhase(
             label = "Benchmark Warmup",
             measurementCount = 1,
             loopMode = LoopMode.Warmup(warmupManager),
-            gcBeforePhase = true,
-            ignorePauseEvent = true
+            metrics = if (collectCpuEventInstructions) {
+                arrayOf(
+                    TimeCapture(),
+                    CpuEventCounterCapture(cpuEventCounter, listOf(Event.Instructions))
+                )
+            } else {
+                arrayOf(TimeCapture())
+            },
+            gcBeforePhase = true
         )
 
         fun timingMeasurementPhase(
             loopMode: LoopMode,
             measurementCount: Int,
-            simplifiedTimingOnlyMode: Boolean
+            simplifiedTimingOnlyMode: Boolean,
+            cpuEventCountersMask: Int,
         ) = MicrobenchmarkPhase(
             label = "Benchmark Time",
             measurementCount = measurementCount,
             loopMode = loopMode,
+            metrics = if (cpuEventCountersMask != 0) {
+                arrayOf(
+                    TimeCapture(),
+                    CpuEventCounterCapture(cpuEventCounter, cpuEventCountersMask)
+                )
+            } else {
+                arrayOf(TimeCapture())
+            },
             thermalThrottleSleepsMax = if (simplifiedTimingOnlyMode) 0 else 2
         )
 
@@ -177,6 +205,7 @@
         val profiler: Profiler?,
         val warmupCount: Int?,
         val measurementCount: Int?,
+        val cpuEventCountersMask: Int,
     ) {
         val warmupManager = WarmupManager(overrideCount = warmupCount)
         init {
@@ -201,12 +230,21 @@
                 // sharing between these phases, we should update that JSON representation.
                 val loopMode = LoopMode.Duration(BenchmarkState.DEFAULT_MEASUREMENT_DURATION_NS)
                 listOfNotNull(
-                    warmupPhase(warmupManager),
+                    warmupPhase(
+                        warmupManager = warmupManager,
+                        // Collect the instructions metric to ensure that behavior and timing aren't
+                        // significantly skewed between warmup and timing phases. For example, if
+                        // only timing phase has a complex impl of pause/resume, then behavior
+                        // changes drastically, and the warmupManager will estimate a far faster
+                        // impl of `measureRepeated { runWithTimingDisabled }`
+                        collectCpuEventInstructions = cpuEventCountersMask != 0
+                    ),
                     // Regular timing phase
                     timingMeasurementPhase(
                         measurementCount = measurementCount ?: 50,
                         loopMode = loopMode,
-                        simplifiedTimingOnlyMode = simplifiedTimingOnlyMode
+                        simplifiedTimingOnlyMode = simplifiedTimingOnlyMode,
+                        cpuEventCountersMask = cpuEventCountersMask
                     ),
                     if (simplifiedTimingOnlyMode || profiler == null) {
                         null
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
index 299db34..ac9027a 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
@@ -21,6 +21,7 @@
 import android.os.Debug
 import android.util.Log
 import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
 import androidx.annotation.VisibleForTesting
 import androidx.benchmark.BenchmarkState.Companion.TAG
 import androidx.benchmark.Outputs.dateToFileName
@@ -43,18 +44,32 @@
  * avoid these however, in order to avoid the runtime visiting a new class in the hot path, when
  * switching from warmup -> timing phase, when [start] would be called.
  */
-internal sealed class Profiler {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+sealed class Profiler {
     class ResultFile(
         val label: String,
         val outputRelativePath: String,
-        val source: Profiler
+        val source: Profiler?
     ) {
+        constructor(
+            label: String,
+            absolutePath: String
+        ) : this(
+            label = label,
+            outputRelativePath = Outputs.relativePathFor(absolutePath),
+            source = null
+        )
+
         fun embedInPerfettoTrace(perfettoTracePath: String) {
-            source.embedInPerfettoTrace(
+            source?.embedInPerfettoTrace(
                 File(Outputs.outputDirectory, outputRelativePath),
                 File(perfettoTracePath)
             )
         }
+        val sanitizedOutputRelativePath: String
+            get() = outputRelativePath
+                .replace("(", "\\(")
+                .replace(")", "\\)")
     }
 
     abstract fun start(traceUniqueName: String): ResultFile?
diff --git a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoTraceRule.kt b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoTraceRule.kt
index d8dcd99..c3c0a27 100644
--- a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoTraceRule.kt
+++ b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoTraceRule.kt
@@ -18,7 +18,7 @@
 
 import android.os.Build
 import androidx.benchmark.InstrumentationResults
-import androidx.benchmark.Outputs
+import androidx.benchmark.Profiler
 import androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi
 import androidx.benchmark.perfetto.PerfettoTrace
 import androidx.test.platform.app.InstrumentationRegistry
@@ -94,15 +94,10 @@
                     appTagPackages = if (enableAppTagTracing) listOf(thisPackage) else emptyList(),
                     userspaceTracingPackage = if (enableUserspaceTracing) thisPackage else null,
                     traceCallback = {
-                        val relativePath = Outputs.relativePathFor(it.path)
-                            .replace("(", "\\(")
-                            .replace(")", "\\)")
                         InstrumentationResults.instrumentationReport {
-                            ideSummaryRecord(
-                                // Can't link, simply print path
-                                summaryV1 = "Trace written to device at ${it.path}",
-                                // Link the trace within Studio
-                                summaryV2 = "[$label Trace](file://$relativePath)"
+                            reportSummaryToIde(
+                                testName = label,
+                                profilerResults = listOf(Profiler.ResultFile("Trace", it.path))
                             )
                         }
                         traceCallback?.invoke(it)
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/FileLinkingRule.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/FileLinkingRule.kt
index e3560f6..c5781b3 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/FileLinkingRule.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/FileLinkingRule.kt
@@ -107,10 +107,7 @@
 
         if (Outputs.outputDirectory == Outputs.dirUsableByAppAndShell) {
             InstrumentationResults.instrumentationReport {
-                ideSummaryRecord(
-                    summaryV1 = "", // not supported
-                    summaryV2 = summaryString.trim()
-                )
+                reportSummaryToIde(message = summaryString.trim())
             }
         } else {
             Log.d(TAG, "FileLinkingRule doesn't support outputDirectory != dirUsableByAppAndShell")
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/IdeSummaryStringTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/IdeSummaryStringTest.kt
deleted file mode 100644
index 1a07546..0000000
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/IdeSummaryStringTest.kt
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright 2021 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.benchmark.macro
-
-import androidx.benchmark.BenchmarkResult
-import androidx.benchmark.MetricResult
-import androidx.benchmark.Outputs
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import java.io.File
-import kotlin.test.assertFailsWith
-import org.junit.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-public class IdeSummaryStringTest {
-    private fun createAbsoluteTracePaths(
-        @Suppress("SameParameterValue") count: Int
-    ) = List(count) {
-        File(Outputs.dirUsableByAppAndShell, "iter$it.trace").absolutePath
-    }
-
-    @Test
-    public fun singleMinimal() {
-        val metricResult = MetricResult("Metric", listOf(0.0, 1.1, 2.2))
-
-        assertEquals(0, metricResult.minIndex)
-        assertEquals(1, metricResult.medianIndex)
-        assertEquals(2, metricResult.maxIndex)
-        val absoluteTracePaths = createAbsoluteTracePaths(3)
-        val (summaryV1, summaryV2) = ideSummaryStrings(
-            warningLines = "",
-            benchmarkName = "foo",
-            measurements = BenchmarkResult.Measurements(
-                singleMetrics = listOf(metricResult),
-                sampledMetrics = emptyList()
-            ),
-            absoluteTracePaths = absoluteTracePaths
-        )
-        assertEquals(
-            """
-                |foo
-                |  Metric   min 0.0,   median 1.1,   max 2.2
-                |
-            """.trimMargin(),
-            summaryV1
-        )
-        assertEquals(
-            """
-                |foo
-                |  Metric   [min 0.0](file://iter0.trace),   [median 1.1](file://iter1.trace),   [max 2.2](file://iter2.trace)
-                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
-                |
-            """.trimMargin(),
-            summaryV2
-        )
-    }
-
-    @Test
-    public fun singleComplex() {
-        val metric1 = MetricResult("Metric1", listOf(0.0, 1.0, 2.0))
-        val metric2 = MetricResult("Metric2", listOf(222.0, 111.0, 0.0))
-        val absoluteTracePaths = createAbsoluteTracePaths(3)
-        val (summaryV1, summaryV2) = ideSummaryStrings(
-            warningLines = "",
-            benchmarkName = "foo",
-            measurements = BenchmarkResult.Measurements(
-                singleMetrics = listOf(metric1, metric2),
-                sampledMetrics = emptyList()
-            ),
-            absoluteTracePaths = absoluteTracePaths
-        )
-        assertEquals(
-            """
-                |foo
-                |  Metric1   min   0.0,   median   1.0,   max   2.0
-                |  Metric2   min   0.0,   median 111.0,   max 222.0
-                |
-            """.trimMargin(),
-            summaryV1
-        )
-        assertEquals(
-            """
-                |foo
-                |  Metric1   [min   0.0](file://iter0.trace),   [median   1.0](file://iter1.trace),   [max   2.0](file://iter2.trace)
-                |  Metric2   [min   0.0](file://iter2.trace),   [median 111.0](file://iter1.trace),   [max 222.0](file://iter0.trace)
-                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
-                |
-            """.trimMargin(),
-            summaryV2
-        )
-    }
-
-    @Test
-    public fun sampledMinimal() {
-        val metricResult = MetricResult("Metric1", List(101) { it.toDouble() })
-        val absoluteTracePaths = createAbsoluteTracePaths(3)
-        val (summaryV1, summaryV2) = ideSummaryStrings(
-            warningLines = "",
-            benchmarkName = "foo",
-            measurements = BenchmarkResult.Measurements(
-                singleMetrics = emptyList(),
-                sampledMetrics = listOf(metricResult)
-            ),
-            absoluteTracePaths = absoluteTracePaths
-        )
-        assertEquals(
-            """
-                |foo
-                |  Metric1   P50   50.0,   P90   90.0,   P95   95.0,   P99   99.0
-                |
-            """.trimMargin(),
-            summaryV1
-        )
-        assertEquals(
-            """
-                |foo
-                |  Metric1   P50   50.0,   P90   90.0,   P95   95.0,   P99   99.0
-                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
-                |
-            """.trimMargin(),
-            summaryV2
-        )
-    }
-
-    @Test
-    public fun complex() {
-        val single = MetricResult("Metric1", listOf(0.0, 1.0, 2.0))
-        val sampled = MetricResult("Metric2", List(101) { it.toDouble() })
-        val absoluteTracePaths = createAbsoluteTracePaths(3)
-        val (summaryV1, summaryV2) = ideSummaryStrings(
-            warningLines = "",
-            benchmarkName = "foo",
-            measurements = BenchmarkResult.Measurements(
-                singleMetrics = listOf(single),
-                sampledMetrics = listOf(sampled)
-            ),
-            absoluteTracePaths = absoluteTracePaths
-        )
-        assertEquals(
-            """
-                |foo
-                |  Metric1   min   0.0,   median   1.0,   max   2.0
-                |  Metric2   P50   50.0,   P90   90.0,   P95   95.0,   P99   99.0
-                |
-            """.trimMargin(),
-            summaryV1
-        )
-        assertEquals(
-            """
-                |foo
-                |  Metric1   [min   0.0](file://iter0.trace),   [median   1.0](file://iter1.trace),   [max   2.0](file://iter2.trace)
-                |  Metric2   P50   50.0,   P90   90.0,   P95   95.0,   P99   99.0
-                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
-                |
-            """.trimMargin(),
-            summaryV2
-        )
-    }
-
-    @Test
-    public fun warning() {
-        val metricResult = MetricResult("Metric", listOf(0.0, 1.0, 2.0))
-        val absoluteTracePaths = createAbsoluteTracePaths(3)
-        val (summaryV1, summaryV2) = ideSummaryStrings(
-            warningLines = "warning\nstring\n",
-            benchmarkName = "foo",
-            measurements = BenchmarkResult.Measurements(
-                singleMetrics = listOf(metricResult),
-                sampledMetrics = emptyList()
-            ),
-            absoluteTracePaths = absoluteTracePaths
-        )
-        assertEquals(
-            """
-                |warning
-                |string
-                |foo
-                |  Metric   min 0.0,   median 1.0,   max 2.0
-                |
-            """.trimMargin(),
-            summaryV1
-        )
-        assertEquals(
-            """
-                |warning
-                |string
-                |foo
-                |  Metric   [min 0.0](file://iter0.trace),   [median 1.0](file://iter1.trace),   [max 2.0](file://iter2.trace)
-                |    Traces: Iteration [0](file://iter0.trace) [1](file://iter1.trace) [2](file://iter2.trace)
-                |
-            """.trimMargin(),
-            summaryV2
-        )
-    }
-
-    @Test
-    public fun requireNotEmpty() {
-        assertFailsWith<IllegalArgumentException> {
-            ideSummaryStrings(
-                warningLines = "",
-                benchmarkName = "foo",
-                measurements = BenchmarkResult.Measurements(
-                    singleMetrics = emptyList(),
-                    sampledMetrics = emptyList()
-                ),
-                absoluteTracePaths = emptyList()
-            ).first
-        }
-    }
-}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
index cc85297..4382f0d 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
@@ -220,8 +220,12 @@
         profileTsPath = tsAbsolutePath
     )
     InstrumentationResults.instrumentationReport {
-        val summary = summaryRecord(results)
-        ideSummaryRecord(summaryV1 = summary, summaryV2 = summary)
+        // Ideally would link trace as a profiler result for consistency with other codepaths,
+        // but we don't to change BP's custom link appearance to the default simple one
+        reportSummaryToIde(
+            testName = uniqueFilePrefix,
+            message = summaryRecord(results),
+        )
         Log.d(TAG, "Total Run Time Ns: $totalRunTime")
     }
 }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/IdeSummaryString.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/IdeSummaryString.kt
deleted file mode 100644
index fd1de64..0000000
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/IdeSummaryString.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2021 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.benchmark.macro
-
-import androidx.benchmark.BenchmarkResult
-import androidx.benchmark.MetricResult
-import androidx.benchmark.Outputs
-
-/**
- * Returns a pair of ideSummaryStrings - v1 (pre Arctic-fox) and v2 (Arctic-fox+)
- *
- * These strings are to be displayed in Studio, depending on the version.
- *
- * The V2 string embeds links to trace files relative to the output path sent to the IDE via
- * `[link](file://<relative/path/to/trace>)`
- *
- * @see androidx.benchmark.InstrumentationResultScope#ideSummaryRecord
- */
-internal fun ideSummaryStrings(
-    warningLines: String,
-    benchmarkName: String,
-    measurements: BenchmarkResult.Measurements,
-    absoluteTracePaths: List<String>
-): Pair<String, String> {
-    require(measurements.isNotEmpty()) { "Require non-empty list of metric results." }
-    val allMetrics = measurements.singleMetrics + measurements.sampledMetrics
-
-    val maxLabelLength = allMetrics.maxOf { it.name.length }
-
-    fun Double.toDisplayString() = "%,.1f".format(this)
-
-    // max string length of any printed min/median/max is the largest max value seen. used to pad.
-    val maxValueLength = allMetrics
-        .maxOf { it.max }
-        .toDisplayString().length
-
-    fun ideSummaryString(
-        singleTransform: (
-            name: String,
-            min: String,
-            median: String,
-            max: String,
-            metricResult: MetricResult
-        ) -> String
-    ) = (
-        listOf(warningLines + benchmarkName) +
-            measurements.singleMetrics.map {
-                singleTransform(
-                    it.name.padStart(maxLabelLength),
-                    it.min.toDisplayString().padStart(maxValueLength),
-                    it.median.toDisplayString().padStart(maxValueLength),
-                    it.max.toDisplayString().padStart(maxValueLength),
-                    it
-                )
-            } +
-            measurements.sampledMetrics.map {
-                val name = it.name.padStart(maxLabelLength)
-                val p50 = it.p50.toDisplayString().padStart(maxValueLength)
-                val p90 = it.p90.toDisplayString().padStart(maxValueLength)
-                val p95 = it.p95.toDisplayString().padStart(maxValueLength)
-                val p99 = it.p99.toDisplayString().padStart(maxValueLength)
-                // we don't try and link percentiles, since they're grouped across multiple iters
-                "  $name   P50  $p50,   P90  $p90,   P95  $p95,   P99  $p99"
-            }
-        ).joinToString("\n") + "\n"
-
-    val relativeTracePaths = absoluteTracePaths.map { absolutePath ->
-        Outputs.relativePathFor(absolutePath)
-            .replace("(", "\\(")
-            .replace(")", "\\)")
-    }
-    return Pair(
-        first = ideSummaryString(
-            singleTransform = { name, min, median, max, _ ->
-                "  $name   min $min,   median $median,   max $max"
-            }
-        ),
-        second = ideSummaryString { name, min, median, max, metricResult ->
-            "  $name" +
-                "   [min $min](file://${relativeTracePaths[metricResult.minIndex]})," +
-                "   [median $median](file://${relativeTracePaths[metricResult.medianIndex]})," +
-                "   [max $max](file://${relativeTracePaths[metricResult.maxIndex]})"
-        } + "    Traces: Iteration " + relativeTracePaths.mapIndexed { index, path ->
-            "[$index](file://$path)"
-        }.joinToString(separator = " ") + "\n"
-    )
-}
\ No newline at end of file
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index 788415a..43d2172 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -27,6 +27,7 @@
 import androidx.benchmark.ConfigurationError
 import androidx.benchmark.DeviceInfo
 import androidx.benchmark.InstrumentationResults
+import androidx.benchmark.Profiler
 import androidx.benchmark.ResultWriter
 import androidx.benchmark.Shell
 import androidx.benchmark.UserspaceTracing
@@ -217,6 +218,7 @@
     // output, and give it different (test-wide) lifecycle
     val perfettoCollector = PerfettoCaptureWrapper()
     val tracePaths = mutableListOf<String>()
+    val resultFiles = mutableListOf<Profiler.ResultFile>()
     try {
         metrics.forEach {
             it.configure(packageName)
@@ -273,7 +275,12 @@
                                 it.stop()
                             }
                             if (launchWithMethodTracing) {
-                                scope.stopMethodTracing()
+                                val (label, tracePath) = scope.stopMethodTracing()
+                                val resultFile = Profiler.ResultFile(
+                                    label = label,
+                                    absolutePath = tracePath
+                                )
+                                resultFiles += resultFile
                             }
                         }
                     }
@@ -328,13 +335,14 @@
             """.trimIndent()
         }
         InstrumentationResults.instrumentationReport {
-            val (summaryV1, summaryV2) = ideSummaryStrings(
-                warningMessage,
-                uniqueName,
-                measurements,
-                tracePaths
+            reportSummaryToIde(
+                warningMessage = warningMessage,
+                testName = uniqueName,
+                measurements = measurements,
+                iterationTracePaths = tracePaths,
+                profilerResults = resultFiles
             )
-            ideSummaryRecord(summaryV1 = summaryV1, summaryV2 = summaryV2)
+
             warningMessage = "" // warning only printed once
             measurements.singleMetrics.forEach {
                 it.putInBundle(bundle, suppressionState?.prefix ?: "")
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
index c15cccc..12c949c 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
@@ -287,9 +287,11 @@
     /**
      * Stops method tracing for the given [packageName] and copies the output to the
      * `additionalTestOutputDir`.
+     *
+     * @return a [Pair] representing the label, and the absolute path of the method trace.
      */
     @SuppressLint("BanThreadSleep") // Need to sleep to wait for the traces to be flushed.
-    internal fun stopMethodTracing() {
+    internal fun stopMethodTracing(): Pair<String, String> {
         Shell.executeScriptSilent("am profile stop $packageName")
         // Wait for the profiles to get dumped :(
         // ART Method tracing has a buffer size of 8M, so 1 second should be enough
@@ -309,14 +311,15 @@
         val stagingFile = File.createTempFile("methodTrace", null, Outputs.dirUsableByAppAndShell)
         // Staging location before we write it again using Outputs.writeFile(...)
         Shell.executeScriptSilent("cp '$tracePath' '$stagingFile'")
-        // Report
-        Outputs.writeFile(fileName, fileName) {
+        // Report(
+        val outputPath = Outputs.writeFile(fileName, fileName) {
             Log.d(TAG, "Writing method traces to ${it.absolutePath}")
             stagingFile.copyTo(it, overwrite = true)
             // Cleanup
             stagingFile.delete()
             Shell.executeScriptSilent("rm \"$tracePath\"")
         }
+        return fileName to outputPath
     }
 
     /**
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/CpuCounterBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/CpuCounterBenchmark.kt
deleted file mode 100644
index 3ce5c8b..0000000
--- a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/CpuCounterBenchmark.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.benchmark.benchmark
-
-import android.os.Build
-import androidx.benchmark.CpuCounter
-import androidx.benchmark.DeviceInfo
-import androidx.benchmark.PropOverride
-import androidx.benchmark.Shell
-import androidx.benchmark.junit4.BenchmarkRule
-import androidx.benchmark.junit4.measureRepeated
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
-import org.junit.After
-import org.junit.Assume
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 21)
-class CpuCounterBenchmark {
-    @get:Rule
-    val benchmarkRule = BenchmarkRule()
-    private val values = CpuCounter.Values()
-
-    private val perfHardenProp = PropOverride("security.perf_harden", "0")
-    private var shouldResetEnforce1 = false
-
-    @Before
-    fun before() {
-        // TODO: make this automatic
-        if (Build.VERSION.SDK_INT > 29) {
-            val blockedBySelinux = Shell.isSELinuxEnforced()
-            Assume.assumeTrue(
-                "blocked by selinux = $blockedBySelinux, rooted = ${DeviceInfo.isRooted}",
-                !blockedBySelinux || DeviceInfo.isRooted
-            )
-            if (blockedBySelinux && DeviceInfo.isRooted) {
-                Shell.executeScriptSilent("setenforce 0")
-                shouldResetEnforce1 = true
-            }
-            perfHardenProp.forceValue()
-        }
-        val error = CpuCounter.checkPerfEventSupport()
-        Assume.assumeTrue(error, error == null)
-    }
-
-    @After
-    fun after() {
-        perfHardenProp.resetIfOverridden()
-        if (shouldResetEnforce1) {
-            Shell.executeScriptSilent("setenforce 1")
-        }
-    }
-
-    /**
-     * Measures overhead of starting and stopping
-     *
-     * We can expect to see some portion of this impact measurements directtly.
-     */
-    @Test
-    fun startStopOnly() = CpuCounter().use { counter ->
-        counter.resetEvents(
-            listOf(
-                CpuCounter.Event.CpuCycles,
-                CpuCounter.Event.L1IMisses,
-                CpuCounter.Event.Instructions,
-            )
-        )
-        benchmarkRule.measureRepeated {
-            runWithTimingDisabled {
-                counter.reset()
-            }
-            counter.start()
-            counter.stop()
-        }
-    }
-
-    /**
-     * Measures full per measurement iteration cost
-     *
-     * This is important not because of direct intrusiveness to timing measurements, but because it
-     * may correlate with other intrusiveness, e.g. cache interference from reset/reading values
-     */
-    @Test
-    fun perIterationCost() = CpuCounter().use { counter ->
-        counter.resetEvents(
-            listOf(
-                CpuCounter.Event.CpuCycles,
-                CpuCounter.Event.L1IMisses,
-                CpuCounter.Event.Instructions,
-            )
-        )
-        var out = 0L
-        benchmarkRule.measureRepeated {
-            counter.reset()
-            counter.start()
-            counter.stop()
-            counter.read(values)
-            out += values.getValue(CpuCounter.Event.CpuCycles)
-            out += values.getValue(CpuCounter.Event.L1IMisses)
-            out += values.getValue(CpuCounter.Event.Instructions)
-        }
-    }
-}
diff --git a/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/CpuEventCounterBenchmark.kt b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/CpuEventCounterBenchmark.kt
new file mode 100644
index 0000000..e6ebe5a
--- /dev/null
+++ b/benchmark/benchmark/src/androidTest/java/androidx/benchmark/benchmark/CpuEventCounterBenchmark.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.benchmark
+
+import androidx.benchmark.CpuEventCounter
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 21)
+class CpuEventCounterBenchmark {
+    @get:Rule
+    val benchmarkRule = BenchmarkRule()
+    private val values = CpuEventCounter.Values()
+
+    @Before
+    fun before() {
+        // skip test if need root, or event fails to enable
+        CpuEventCounter.forceEnable()?.let { errorMessage ->
+            assumeTrue(errorMessage, false)
+        }
+    }
+
+    @After
+    fun after() {
+        CpuEventCounter.reset()
+    }
+
+    /**
+     * Measures overhead of starting and stopping
+     *
+     * We can expect to see some portion of this impact measurements directtly.
+     */
+    @Test
+    fun startStopOnly() = CpuEventCounter().use { counter ->
+        counter.resetEvents(
+            listOf(
+                CpuEventCounter.Event.CpuCycles,
+                CpuEventCounter.Event.L1IMisses,
+                CpuEventCounter.Event.Instructions,
+            )
+        )
+        benchmarkRule.measureRepeated {
+            runWithTimingDisabled {
+                counter.reset()
+            }
+            counter.start()
+            counter.stop()
+        }
+    }
+
+    /**
+     * Measures full per measurement iteration cost
+     *
+     * This is important not because of direct intrusiveness to timing measurements, but because it
+     * may correlate with other intrusiveness, e.g. cache interference from reset/reading values
+     */
+    @Test
+    fun perIterationCost() = CpuEventCounter().use { counter ->
+        counter.resetEvents(
+            listOf(
+                CpuEventCounter.Event.CpuCycles,
+                CpuEventCounter.Event.L1IMisses,
+                CpuEventCounter.Event.Instructions,
+            )
+        )
+        var out = 0L
+        benchmarkRule.measureRepeated {
+            counter.reset()
+            counter.start()
+            counter.stop()
+            counter.read(values)
+            out += values.getValue(CpuEventCounter.Event.CpuCycles)
+            out += values.getValue(CpuEventCounter.Event.L1IMisses)
+            out += values.getValue(CpuEventCounter.Event.Instructions)
+        }
+    }
+}
diff --git a/biometric/biometric-ktx/OWNERS b/biometric/biometric-ktx/OWNERS
index 71f000e..2d066b3 100644
--- a/biometric/biometric-ktx/OWNERS
+++ b/biometric/biometric-ktx/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 483659
 kchyn@google.com
 curtislb@google.com
\ No newline at end of file
diff --git a/browser/browser/src/main/res/values-ne/strings.xml b/browser/browser/src/main/res/values-ne/strings.xml
index e25c206..b166614 100644
--- a/browser/browser/src/main/res/values-ne/strings.xml
+++ b/browser/browser/src/main/res/values-ne/strings.xml
@@ -19,5 +19,5 @@
     <string name="fallback_menu_item_open_in_browser" msgid="3413186855122069269">"ब्राउजरमा खोल्नुहोस्"</string>
     <string name="fallback_menu_item_copy_link" msgid="4566929209979330987">"लिंक प्रतिलिपि गर्नुहोस्"</string>
     <string name="fallback_menu_item_share_link" msgid="7145444925855055364">"लिंक सेयर गर्नुहोस्"</string>
-    <string name="copy_toast_msg" msgid="3260749812566568062">"क्लिपबोर्डमा लिंक प्रतिलिपि गरियो"</string>
+    <string name="copy_toast_msg" msgid="3260749812566568062">"क्लिपबोर्डमा लिंक कपी गरियो"</string>
 </resources>
diff --git a/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt b/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt
index 4e3e027..799ebed 100644
--- a/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/testConfiguration/AndroidTestConfigBuilderTest.kt
@@ -16,6 +16,8 @@
 
 package androidx.build.testConfiguration
 
+import java.io.StringReader
+import javax.xml.parsers.SAXParserFactory
 import org.hamcrest.CoreMatchers
 import org.hamcrest.MatcherAssert
 import org.junit.Before
@@ -24,8 +26,6 @@
 import org.junit.runners.JUnit4
 import org.xml.sax.InputSource
 import org.xml.sax.helpers.DefaultHandler
-import java.io.StringReader
-import javax.xml.parsers.SAXParserFactory
 
 /**
  * Simple check that the test config templates are able to be parsed as valid xml.
@@ -251,7 +251,6 @@
 
     @Test
     fun testValidTestConfigXml_runAllTests() {
-        builder.runAllTests(false)
         validate(builder.buildXml())
     }
 
diff --git a/buildSrc/OWNERS b/buildSrc/OWNERS
index ec63c9a..be64598 100644
--- a/buildSrc/OWNERS
+++ b/buildSrc/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 461356
 set noparent
 
 jeffrygaston@google.com
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 755dff6..d08325e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -298,6 +298,7 @@
             if (project.path.contains("-ktx")) return false
             if (project.path.contains("compose")) return false
             if (project.path.startsWith(":ui")) return false
+            if (project.path.startsWith(":text:text")) return false
             return field
         }
     private var licenses: MutableCollection<License> = ArrayList()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 5f78595..1322707 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -299,6 +299,7 @@
 
         project.afterEvaluate {
             project.tasks.withType(KotlinCompile::class.java).configureEach { task ->
+
                 if (extension.type == LibraryType.COMPILER_PLUGIN) {
                     task.kotlinOptions.jvmTarget = "11"
                 } else if (extension.type.compilationTarget == CompilationTarget.HOST &&
@@ -315,6 +316,20 @@
                 if (!project.name.contains("camera-camera2-pipe")) {
                     kotlinCompilerArgs += "-Xjvm-default=all"
                 }
+                if (!extension.targetsJavaConsumers) {
+                    // The Kotlin Compiler adds intrinsic assertions which are only relevant
+                    // when the code is consumed by Java users. Therefore we can turn this off
+                    // when code is being consumed by Kotlin users.
+
+                    // Additional Context:
+                    // https://github.com/JetBrains/kotlin/blob/master/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt#L239
+                    // b/280633711
+                    kotlinCompilerArgs += listOf(
+                        "-Xno-param-assertions",
+                        "-Xno-call-assertions",
+                        "-Xno-receiver-assertions"
+                    )
+                }
                 task.kotlinOptions.freeCompilerArgs += kotlinCompilerArgs
             }
 
@@ -345,9 +360,13 @@
             // Disable any source JAR task(s) added by KotlinMultiplatformPlugin.
             // https://youtrack.jetbrains.com/issue/KT-55881
             project.tasks.withType(Jar::class.java).configureEach { jarTask ->
-                if (jarTask.name.endsWith("SourcesJar") &&
-                    jarTask.description?.startsWith("Assembles a jar archive") == true) {
-                    jarTask.enabled = false
+                if (jarTask.name == "jvmSourcesJar") {
+                    // We can't set duplicatesStrategy directly on the Jar task since it will get
+                    // overridden when the KotlinMultiplatformPlugin creates child specs, but we
+                    // can set it on a per-file basis.
+                    jarTask.eachFile { fileCopyDetails ->
+                        fileCopyDetails.duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+                    }
                 }
             }
         }
@@ -634,6 +653,9 @@
         defaultConfig.targetSdk = TARGET_SDK_VERSION
         ndkVersion = SupportConfig.NDK_VERSION
 
+        // Suppress output of android:compileSdkVersion and related attributes (b/277836549).
+        aaptOptions.additionalParameters += "--no-compile-sdk-metadata"
+
         defaultConfig.testInstrumentationRunner = INSTRUMENTATION_RUNNER
 
         testOptions.animationsDisabled = true
@@ -758,6 +780,18 @@
             if (androidXExtension.shouldRelease()) {
                 project.extra.set("publish", true)
             }
+            if (project.hasBenchmarkPlugin()) {
+                // Inject AOT compilation - see b/287358254 for context, b/288167775 for AGP support
+
+                // NOTE: we assume here that all benchmarks have package name $namespace.test
+                val aotCompile = "cmd package compile -m speed -f $namespace.test"
+
+                // only run aotCompile on N+, where it's supported
+                val inject = "if [ `getprop ro.build.version.sdk` -ge 24 ]; then $aotCompile; fi"
+                val options =
+                    "/data/local/tmp/${project.name}-$testBuildType-androidTest.apk && $inject #"
+                adbOptions.setInstallOptions(*options.split(" ").toTypedArray())
+            }
         }
     }
 
@@ -975,7 +1009,6 @@
         const val CREATE_LIBRARY_BUILD_INFO_FILES_TASK = "createLibraryBuildInfoFiles"
         const val GENERATE_TEST_CONFIGURATION_TASK = "GenerateTestConfiguration"
         const val ZIP_TEST_CONFIGS_WITH_APKS_TASK = "zipTestConfigsWithApks"
-        const val ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK = "zipConstrainedTestConfigsWithApks"
 
         const val TASK_GROUP_API = "API"
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index fd28686..9f71c14 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -17,7 +17,6 @@
 package androidx.build
 
 import androidx.build.AndroidXImplPlugin.Companion.CREATE_LIBRARY_BUILD_INFO_FILES_TASK
-import androidx.build.AndroidXImplPlugin.Companion.ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK
 import androidx.build.AndroidXImplPlugin.Companion.ZIP_TEST_CONFIGS_WITH_APKS_TASK
 import androidx.build.buildInfo.CreateAggregateLibraryBuildInfoFileTask
 import androidx.build.buildInfo.CreateAggregateLibraryBuildInfoFileTask.Companion.CREATE_AGGREGATE_BUILD_INFO_FILES_TASK
@@ -151,18 +150,6 @@
             it.isZip64 = true
             it.isReproducibleFileOrder = true
         }
-        project.tasks.register(
-            ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK, Zip::class.java
-        ) {
-            it.destinationDirectory.set(project.getDistributionDirectory())
-            it.archiveFileName.set("constrainedAndroidTest.zip")
-            it.from(project.getConstrainedTestConfigDirectory())
-            // We're mostly zipping a bunch of .apk files that are already compressed
-            it.entryCompression = ZipEntryCompression.STORED
-            // Archive is greater than 4Gb :O
-            it.isZip64 = true
-            it.isReproducibleFileOrder = true
-        }
 
         AffectedModuleDetector.configure(gradle, this)
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
index 8c5ef8b..d2c8f17 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
@@ -109,7 +109,7 @@
             appApk.elements.single().outputFile
         } else {
             "gs://androidx-ftl-test-results/github-ci-action/placeholderApp/" +
-                "aadb5e0219ce132e73236ef1e06bb50dd60217e20e803ea00d57a1cf1cea902c.apk"
+                "37728671722adb4f49b23ed2f0edb0b4def51c841b0735fdd1648942ff1e9090.apk"
         }
         try {
             execOperations.exec {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Ktfmt.kt b/buildSrc/private/src/main/kotlin/androidx/build/Ktfmt.kt
index 0eebdd7..07f00a5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Ktfmt.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Ktfmt.kt
@@ -29,6 +29,7 @@
 import org.gradle.api.tasks.Classpath
 import org.gradle.api.tasks.InputFiles
 import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.OutputFiles
 import org.gradle.api.tasks.PathSensitive
 import org.gradle.api.tasks.PathSensitivity
 import org.gradle.api.tasks.TaskAction
@@ -37,7 +38,6 @@
 fun Project.configureKtfmt() {
     tasks.register("ktFormat", KtfmtFormatTask::class.java) { task ->
         task.ktfmtClasspath.from(getKtfmtConfiguration())
-        task.cacheEvenIfNoOutputs()
     }
     tasks.register("ktCheck", KtfmtCheckTask::class.java) { task ->
         task.ktfmtClasspath.from(getKtfmtConfiguration())
@@ -123,6 +123,10 @@
         group = "formatting"
     }
 
+    // Format task rewrites inputs, so the outputs are the same as inputs.
+    @OutputFiles
+    fun getRewrittenFiles(): FileTree = getInputFiles()
+
     @TaskAction
     fun runFormat() {
         if (getInputFiles().files.isEmpty()) return
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index 19752f0..6dc5369e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -297,7 +297,7 @@
     val kotlinComponent = components.findByName("kotlin") as SoftwareComponentInternal
     withSourcesComponents(
         componentFactory,
-        setOf("androidxSourcesElements")
+        setOf("androidxSourcesElements", "libraryVersionMetadata")
     ) { sourcesComponents ->
         configure<PublishingExtension> {
             publications { pubs ->
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/PublishingHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/PublishingHelper.kt
new file mode 100644
index 0000000..cdc48b0
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/PublishingHelper.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.component.AdhocComponentWithVariants
+
+internal fun Project.registerAsComponentForPublishing(gradleVariant: Configuration) {
+    // Android Library project 'release' component
+    val release = components.findByName("release")
+    if (release is AdhocComponentWithVariants) {
+        release.addVariantsFromConfiguration(gradleVariant) { }
+    }
+    // Java Library project 'java' component
+    val javaComponent = components.findByName("java")
+    if (javaComponent is AdhocComponentWithVariants) {
+        javaComponent.addVariantsFromConfiguration(gradleVariant) { }
+    }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
index 45894a5..ace74db 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
@@ -25,12 +25,10 @@
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
 import org.gradle.api.Project
-import org.gradle.api.artifacts.Configuration
 import org.gradle.api.attributes.Bundling
 import org.gradle.api.attributes.Category
 import org.gradle.api.attributes.DocsType
 import org.gradle.api.attributes.Usage
-import org.gradle.api.component.AdhocComponentWithVariants
 import org.gradle.api.file.DuplicatesStrategy
 import org.gradle.api.plugins.JavaPluginExtension
 import org.gradle.api.tasks.CacheableTask
@@ -206,19 +204,6 @@
     }
 }
 
-private fun Project.registerAsComponentForPublishing(gradleVariant: Configuration) {
-    // Android Library project 'release' component
-    val release = components.findByName("release")
-    if (release is AdhocComponentWithVariants) {
-        release.addVariantsFromConfiguration(gradleVariant) { }
-    }
-    // Java Library project 'java' component
-    val javaComponent = components.findByName("java")
-    if (javaComponent is AdhocComponentWithVariants) {
-        javaComponent.addVariantsFromConfiguration(gradleVariant) { }
-    }
-}
-
 /**
  * Finds the main compilation for a source set, usually called 'main' but for android we need to
  * search for 'debug' instead.
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 7f83d15..488394f 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -100,11 +100,6 @@
      */
     abstract fun getSubset(projectPath: String): ProjectSubset
 
-    fun getSubset(task: Task): ProjectSubset {
-        val projectPath = getProjectPathFromTaskPath(task.path)
-        return getSubset(projectPath)
-    }
-
     fun getProjectPathFromTaskPath(taskPath: String): String {
         val lastColonIndex = taskPath.lastIndexOf(":")
         val projectPath = taskPath.substring(0, lastColonIndex)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/GenerateApiLevels.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/GenerateApiLevels.kt
index 4901083..80ec727 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/GenerateApiLevels.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/GenerateApiLevels.kt
@@ -18,7 +18,14 @@
 
 import androidx.build.Version
 import androidx.build.checkapi.ApiLocation
+import androidx.build.registerAsComponentForPublishing
 import java.io.File
+import org.gradle.api.Project
+import org.gradle.api.attributes.Bundling
+import org.gradle.api.attributes.Category
+import org.gradle.api.attributes.Usage
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.kotlin.dsl.named
 
 /**
  * Returns the API files that should be used to generate the API levels metadata.
@@ -84,3 +91,44 @@
     v1.major == v2.major && v1.minor == v2.minor
 
 private fun Version.roundUp() = Version(this.major, this.minor, this.patch)
+
+/**
+ * Usage attribute to specify the version metadata component.
+ */
+internal val Project.versionMetadataUsage: Usage
+    get() = objects.named("library-version-metadata")
+
+/**
+ * Creates a component for the version metadata JSON and registers it for publishing.
+ */
+internal fun Project.registerVersionMetadataComponent(
+    generateApiTask: TaskProvider<GenerateApiTask>
+) {
+    configurations.create("libraryVersionMetadata") { configuration ->
+        configuration.isVisible = false
+        configuration.isCanBeResolved = false
+
+        configuration.attributes.attribute(
+            Usage.USAGE_ATTRIBUTE,
+            versionMetadataUsage
+        )
+        configuration.attributes.attribute(
+            Category.CATEGORY_ATTRIBUTE,
+            objects.named<Category>(Category.DOCUMENTATION)
+        )
+        configuration.attributes.attribute(
+            Bundling.BUNDLING_ATTRIBUTE,
+            objects.named<Bundling>(Bundling.EXTERNAL)
+        )
+
+        // The generate API task has many output files, only add the version metadata as an artifact
+        val levelsFile = generateApiTask.map { task ->
+            task.apiLocation.map { location ->
+                location.apiLevelsFile
+            }
+        }
+        configuration.outgoing.artifact(levelsFile)
+
+        registerAsComponentForPublishing(configuration)
+    }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
index fbe8cf4..0b63a70 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
@@ -72,6 +72,7 @@
             // using it to validate the generated api
             task.mustRunAfter("updateApiLintBaseline")
         }
+        project.registerVersionMetadataComponent(generateApi)
 
         // Policy: If the artifact has previously been released, e.g. has a beta or later API file
         // checked in, then we must verify "release compatibility" against the work-in-progress
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt
index ada9b8b..9b1016d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/AndroidTestConfigBuilder.kt
@@ -26,8 +26,6 @@
     var isBenchmark: Boolean = false
     var isPostsubmit: Boolean = true
     lateinit var minSdk: String
-    var runAllTests: Boolean = true
-    var cleanupApks: Boolean = true
     val tags = mutableListOf<String>()
     lateinit var testApkName: String
     lateinit var testApkSha256: String
@@ -41,8 +39,6 @@
     fun isBenchmark(isBenchmark: Boolean) = apply { this.isBenchmark = isBenchmark }
     fun isPostsubmit(isPostsubmit: Boolean) = apply { this.isPostsubmit = isPostsubmit }
     fun minSdk(minSdk: String) = apply { this.minSdk = minSdk }
-    fun runAllTests(runAllTests: Boolean) = apply { this.runAllTests = runAllTests }
-    fun cleanupApks(cleanupApks: Boolean) = apply { this.cleanupApks = cleanupApks }
     fun tag(tag: String) = apply { this.tags.add(tag) }
     fun additionalApkKeys(keys: List<String>) = apply { additionalApkKeys.addAll(keys) }
     fun testApkName(testApkName: String) = apply { this.testApkName = testApkName }
@@ -94,7 +90,7 @@
             }
         }
         sb.append(SETUP_INCLUDE)
-            .append(TARGET_PREPARER_OPEN.replace("CLEANUP_APKS", cleanupApks.toString()))
+            .append(TARGET_PREPARER_OPEN.replace("CLEANUP_APKS", "true"))
             .append(APK_INSTALL_OPTION.replace("APK_NAME", testApkName))
         if (!appApkName.isNullOrEmpty())
             sb.append(APK_INSTALL_OPTION.replace("APK_NAME", appApkName!!))
@@ -102,17 +98,7 @@
             .append(TEST_BLOCK_OPEN)
             .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
             .append(PACKAGE_OPTION.replace("APPLICATION_ID", applicationId))
-        if (runAllTests) {
-            sb.append(TEST_BLOCK_CLOSE)
-        } else {
-            sb.append(SMALL_TEST_OPTIONS)
-                .append(TEST_BLOCK_CLOSE)
-                .append(TEST_BLOCK_OPEN)
-            sb.append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
-                .append(PACKAGE_OPTION.replace("APPLICATION_ID", applicationId))
-                .append(MEDIUM_TEST_OPTIONS)
-                .append(TEST_BLOCK_CLOSE)
-        }
+            .append(TEST_BLOCK_CLOSE)
         sb.append(CONFIGURATION_CLOSE)
         return sb.toString()
     }
@@ -291,13 +277,3 @@
     <option name="instrumentation-arg" key="notAnnotation" value="androidx.test.filters.FlakyTest" />
 
 """.trimIndent()
-
-private val SMALL_TEST_OPTIONS = """
-    <option name="size" value="small" />
-
-""".trimIndent()
-
-private val MEDIUM_TEST_OPTIONS = """
-    <option name="size" value="medium" />
-
-""".trimIndent()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt
index 57de24e..6b15cff 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt
@@ -16,7 +16,6 @@
 
 package androidx.build.testConfiguration
 
-import androidx.build.dependencyTracker.ProjectSubset
 import com.android.build.api.variant.BuiltArtifacts
 import com.android.build.api.variant.BuiltArtifactsLoader
 import java.io.File
@@ -74,9 +73,6 @@
     abstract val servicePreviousLoader: Property<BuiltArtifactsLoader>
 
     @get:Input
-    abstract val affectedModuleDetectorSubset: Property<ProjectSubset>
-
-    @get:Input
     abstract val minSdk: Property<Int>
 
     @get:Input
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
index 3f8005e..6e2ac84 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
@@ -16,7 +16,6 @@
 
 package androidx.build.testConfiguration
 
-import androidx.build.dependencyTracker.ProjectSubset
 import com.android.build.api.variant.BuiltArtifactsLoader
 import java.io.File
 import javax.inject.Inject
@@ -81,9 +80,6 @@
     abstract val testRunner: Property<String>
 
     @get:Input
-    abstract val affectedModuleDetectorSubset: Property<ProjectSubset>
-
-    @get:Input
     abstract val presubmit: Property<Boolean>
 
     @get:Input
@@ -99,36 +95,13 @@
     abstract val outputJson: RegularFileProperty
 
     @get:OutputFile
-    abstract val constrainedOutputXml: RegularFileProperty
-
-    @get:OutputFile
     abstract val outputTestApk: RegularFileProperty
 
-    @get:OutputFile
-    abstract val constrainedOutputTestApk: RegularFileProperty
-
     @get:[OutputFile Optional]
     abstract val outputAppApk: RegularFileProperty
 
-    @get:[OutputFile Optional]
-    abstract val constrainedOutputAppApk: RegularFileProperty
-
     @TaskAction
     fun generateAndroidTestZip() {
-        writeConfigFileContent(
-            outputFile = constrainedOutputXml,
-            isConstrained = true,
-        )
-        writeConfigFileContent(
-            outputFile = outputXml,
-            isConstrained = false,
-        )
-    }
-
-    private fun writeConfigFileContent(
-        outputFile: RegularFileProperty,
-        isConstrained: Boolean = false
-    ) {
         /*
         Testing an Android Application project involves 2 APKS: an application to be instrumented,
         and a test APK. Testing an Android Library project involves only 1 APK, since the library
@@ -136,7 +109,7 @@
         configurations testing Android Application projects, so that both APKs get installed.
          */
         val configBuilder = ConfigBuilder()
-        configBuilder.configName = outputFile.asFile.get().name
+        configBuilder.configName = outputXml.asFile.get().name
         if (appLoader.isPresent) {
 
             // Decides where to load the app apk from, depending on whether appFolder or
@@ -155,11 +128,7 @@
                 ?: throw RuntimeException("Cannot load required APK for task: $name")
             // We don't need to check hasBenchmarkPlugin because benchmarks shouldn't have test apps
             val appApkBuiltArtifact = appApk.elements.single()
-            val destinationApk = if (isConstrained) {
-                constrainedOutputAppApk.get().asFile
-            } else {
-                outputAppApk.get().asFile
-            }
+            val destinationApk = outputAppApk.get().asFile
             File(appApkBuiltArtifact.outputFile).copyTo(destinationApk, overwrite = true)
             configBuilder.appApkName(destinationApk.name)
                 .appApkSha256(sha256(File(appApkBuiltArtifact.outputFile)))
@@ -167,34 +136,6 @@
         configBuilder.additionalApkKeys(additionalApkKeys.get())
         val isPresubmit = presubmit.get()
         configBuilder.isPostsubmit(!isPresubmit)
-        // Will be using the constrained configs for all devices api 26 and below.
-        // Don't attempt to remove APKs after testing. We can't remove the apk on API < 27 due to a
-        // platform crash that occurs when handling a PACKAGE_CHANGED broadcast after the package has
-        // been removed. See b/37264334.
-        if (isConstrained) {
-            configBuilder.cleanupApks(false)
-        }
-        when (affectedModuleDetectorSubset.get()) {
-            ProjectSubset.DEPENDENT_PROJECTS -> {
-                // Don't ever run full tests of RV if it is dependent, since they take > 45 minutes
-                if (isConstrained || testProjectPath.get().contains("recyclerview")) {
-                    configBuilder.runAllTests(false)
-                } else {
-                    configBuilder.runAllTests(true)
-                }
-            }
-            ProjectSubset.NONE -> {
-                if (isPresubmit) {
-                    configBuilder.runAllTests(false)
-                } else {
-                    configBuilder.runAllTests(true)
-                }
-            }
-            // in all other cases, if we are building this config we want to run all the tests
-            else -> {
-                configBuilder.runAllTests(true)
-            }
-        }
         // This section adds metadata tags that will help filter runners to specific modules.
         if (hasBenchmarkPlugin.get()) {
             configBuilder.isBenchmark(true)
@@ -215,27 +156,21 @@
         val testApk = testLoader.get().load(testFolder.get())
             ?: throw RuntimeException("Cannot load required APK for task: $name")
         val testApkBuiltArtifact = testApk.elements.single()
-        val destinationApk = if (isConstrained) {
-            constrainedOutputTestApk.get().asFile
-        } else {
-            outputTestApk.get().asFile
-        }
+        val destinationApk = outputTestApk.get().asFile
         File(testApkBuiltArtifact.outputFile).copyTo(destinationApk, overwrite = true)
         configBuilder.testApkName(destinationApk.name)
             .applicationId(testApk.applicationId)
             .minSdk(minSdk.get().toString())
             .testRunner(testRunner.get())
             .testApkSha256(sha256(File(testApkBuiltArtifact.outputFile)))
-        createOrFail(outputFile).writeText(configBuilder.buildXml())
-        if (!isConstrained) {
-            if (!outputJson.asFile.get().name.startsWith("_")) {
-                // Prefixing json file names with _ allows us to collocate these files
-                // inside of the androidTest.zip to make fetching them less expensive.
-                throw GradleException("json output file names are expected to use _ prefix to, " +
-                    "currently set to ${outputJson.asFile.get().name}")
-            }
-            createOrFail(outputJson).writeText(configBuilder.buildJson())
+        createOrFail(outputXml).writeText(configBuilder.buildXml())
+        if (!outputJson.asFile.get().name.startsWith("_")) {
+            // Prefixing json file names with _ allows us to collocate these files
+            // inside of the androidTest.zip to make fetching them less expensive.
+            throw GradleException("json output file names are expected to use _ prefix to, " +
+                "currently set to ${outputJson.asFile.get().name}")
         }
+        createOrFail(outputJson).writeText(configBuilder.buildJson())
     }
 }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index ac5aefff..639e0bf 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -18,11 +18,9 @@
 
 import androidx.build.AndroidXExtension
 import androidx.build.AndroidXImplPlugin
-import androidx.build.AndroidXImplPlugin.Companion.ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK
 import androidx.build.AndroidXImplPlugin.Companion.ZIP_TEST_CONFIGS_WITH_APKS_TASK
 import androidx.build.asFilenamePrefix
 import androidx.build.dependencyTracker.AffectedModuleDetector
-import androidx.build.getConstrainedTestConfigDirectory
 import androidx.build.getSupportRootFolder
 import androidx.build.getTestConfigDirectory
 import androidx.build.hasAndroidTestSourceCode
@@ -81,17 +79,10 @@
         task.outputTestApk.set(
             File(getTestConfigDirectory(), "${path.asFilenamePrefix()}-$variantName.apk")
         )
-        task.constrainedOutputTestApk.set(
-            File(
-                getConstrainedTestConfigDirectory(),
-                "${path.asFilenamePrefix()}-$variantName.apk"
-            )
-        )
         task.additionalApkKeys.set(androidXExtension.additionalDeviceTestApkKeys)
         task.additionalTags.set(androidXExtension.additionalDeviceTestTags)
         task.outputXml.fileValue(File(getTestConfigDirectory(), xmlName))
         task.outputJson.fileValue(File(getTestConfigDirectory(), jsonName))
-        task.constrainedOutputXml.fileValue(File(getConstrainedTestConfigDirectory(), xmlName))
         task.presubmit.set(isPresubmitBuild())
         // Disable work tests on < API 18: b/178127496
         if (path.startsWith(":work:")) {
@@ -103,12 +94,6 @@
         task.hasBenchmarkPlugin.set(hasBenchmarkPlugin)
         task.testRunner.set(testRunner)
         task.testProjectPath.set(path)
-        val detector = AffectedModuleDetector.getInstance(project)
-        task.affectedModuleDetectorSubset.set(
-            project.provider {
-                detector.getSubset(task)
-            }
-        )
         AffectedModuleDetector.configureTaskGuard(task)
     }
     // Disable xml generation for projects that have no test sources
@@ -119,9 +104,7 @@
             it.enabled = androidXExtension.deviceTests.enabled && hasAndroidTestSourceCode()
         }
     }
-    this.rootProject.tasks.findByName(ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
-        .dependsOn(generateTestConfigurationTask)
-    this.rootProject.tasks.findByName(ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK)!!
+    rootProject.tasks.findByName(ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
         .dependsOn(generateTestConfigurationTask)
 }
 
@@ -144,18 +127,6 @@
         filename += "-${variant.name}.apk"
         return File(getTestConfigDirectory(), filename)
     }
-    fun constrainedOutputAppApkFile(
-        variant: Variant,
-        path: String,
-        instrumentationPath: String?
-    ): File {
-        var filename = path.asFilenamePrefix()
-        if (instrumentationPath != null) {
-            filename += "-for-${instrumentationPath.asFilenamePrefix()}"
-        }
-        filename += "-${variant.name}.apk"
-        return File(getConstrainedTestConfigDirectory(), filename)
-    }
 
     // For application modules, the instrumentation apk is generated in the module itself
     extensions.findByType(ApplicationAndroidComponentsExtension::class.java)?.apply {
@@ -169,7 +140,6 @@
 
                 // The target project is the same being evaluated
                 task.outputAppApk.set(outputAppApkFile(variant, path, null))
-                task.constrainedOutputAppApk.set(constrainedOutputAppApkFile(variant, path, null))
             }
         }
     }
@@ -199,9 +169,6 @@
                 task.outputAppApk.set(
                     outputAppApkFile(variant, targetProjectPath, path)
                 )
-                task.constrainedOutputAppApk.set(
-                    constrainedOutputAppApkFile(variant, targetProjectPath, path)
-                )
 
                 task.appFileCollection.from(
                     configurations
@@ -258,9 +225,6 @@
                 task.outputAppApk.set(
                     outputAppApkFile(variant, targetAppProject.path, path)
                 )
-                task.constrainedOutputAppApk.set(
-                    constrainedOutputAppApkFile(variant, targetAppProject.path, path)
-                )
 
                 task.appFileCollection.from(
                     configuration.incoming.artifactView { view ->
@@ -290,17 +254,9 @@
                 GenerateMediaTestConfigurationTask::class.java
             ) { task ->
                 AffectedModuleDetector.configureTaskGuard(task)
-                val detector = AffectedModuleDetector.getInstance(project)
-                task.affectedModuleDetectorSubset.set(
-                    project.provider {
-                        detector.getSubset(task)
-                    }
-                )
             }
             project.rootProject.tasks.findByName(ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
                 .dependsOn(task)
-            project.rootProject.tasks.findByName(ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK)!!
-                .dependsOn(task)
             return task
         } else {
             return parentProject.tasks.withType(GenerateMediaTestConfigurationTask::class.java)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt b/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
index 3b1abc6..4af02e4 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
@@ -65,7 +65,6 @@
     "relocateShadowJar",
     "testDebugUnitTest",
     "verifyDependencyVersions",
-    "zipConstrainedTestConfigsWithApks",
     "zipTestConfigsWithApks",
     "zipHtmlResultsOfTestDebugUnitTest",
     "zipXmlResultsOfTestDebugUnitTest",
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/BuildServerConfiguration.kt b/buildSrc/public/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
index 9ce15c0..27f18c9 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
@@ -84,13 +84,6 @@
     File(rootProject.buildDir, "test-xml-configs")
 
 /**
- * Directory for android test configuration files that get consumed by Tradefed in CI. These
- * "constrained" configs cause only small and medium tests to be run for dependent projects.
- */
-fun Project.getConstrainedTestConfigDirectory(): File =
-    File(rootProject.buildDir, "constrained-test-xml-configs")
-
-/**
  * Directory to put release note files for generate release note tasks.
  */
 fun Project.getReleaseNotesDirectory(): File =
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt b/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
index ac91da5..5a68daf 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
@@ -24,8 +24,6 @@
 object SupportConfig {
     const val DEFAULT_MIN_SDK_VERSION = 14
     const val INSTRUMENTATION_RUNNER = "androidx.test.runner.AndroidJUnitRunner"
-    private const val INTERNAL_BUILD_TOOLS_VERSION = "34.0.0-rc3"
-    private const val PUBLIC_BUILD_TOOLS_VERSION = "34.0.0"
     const val NDK_VERSION = "23.1.7779620"
 
     /**
@@ -55,13 +53,7 @@
      * Note that the value might be different between the internal and external (github) builds.
      */
     @JvmStatic
-    fun buildToolsVersion(project: Project): String {
-        return if (ProjectLayoutType.isPlayground(project)) {
-            PUBLIC_BUILD_TOOLS_VERSION
-        } else {
-            INTERNAL_BUILD_TOOLS_VERSION
-        }
-    }
+    fun buildToolsVersion(@Suppress("UNUSED_PARAMETER") project: Project): String = "34.0.0"
 }
 
 fun Project.getExternalProjectPath(): File {
diff --git a/busytown/androidx_device_tests.sh b/busytown/androidx_device_tests.sh
index f13af7f..0ddc941 100755
--- a/busytown/androidx_device_tests.sh
+++ b/busytown/androidx_device_tests.sh
@@ -7,7 +7,6 @@
 
 export USE_ANDROIDX_REMOTE_BUILD_CACHE=gcp
 
-impl/build.sh zipTestConfigsWithApks zipConstrainedTestConfigsWithApks \
-    zipOwnersFiles createModuleInfo "$@"
+impl/build.sh zipTestConfigsWithApks zipOwnersFiles createModuleInfo "$@"
 
 echo "Completing $0 at $(date)"
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/CameraPipeConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/CameraPipeConfig.kt
index 37239d7..4196f2f 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/CameraPipeConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/CameraPipeConfig.kt
@@ -15,11 +15,15 @@
  */
 package androidx.camera.camera2.pipe.integration
 
+import android.content.Context
 import androidx.annotation.RequiresApi
-import androidx.camera.camera2.pipe.integration.adapter.CameraFactoryAdapter
+import androidx.annotation.RestrictTo
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.integration.adapter.CameraFactoryProvider
 import androidx.camera.camera2.pipe.integration.adapter.CameraSurfaceAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CameraUseCaseAdapter
 import androidx.camera.core.CameraXConfig
+import androidx.camera.core.impl.CameraThreadConfig
 
 /**
  * Convenience class for generating a pre-populated CameraPipe based [CameraXConfig].
@@ -27,14 +31,31 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 class CameraPipeConfig private constructor() {
     companion object {
-
         /**
          * Creates a [CameraXConfig] containing a default CameraPipe implementation for CameraX.
          */
         @JvmStatic
         fun defaultConfig(): CameraXConfig {
+            return from()
+        }
+
+        /**
+         * Creates a [CameraXConfig] using a pre-existing [CameraPipe] instance.
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        @JvmStatic
+        fun from(
+            sharedCameraPipe: CameraPipe? = null,
+            sharedAppContext: Context? = null,
+            sharedThreadConfig: CameraThreadConfig? = null
+        ): CameraXConfig {
+            val cameraFactoryProvider = CameraFactoryProvider(
+                sharedCameraPipe,
+                sharedAppContext,
+                sharedThreadConfig
+            )
             return CameraXConfig.Builder()
-                .setCameraFactoryProvider(::CameraFactoryAdapter)
+                .setCameraFactoryProvider(cameraFactoryProvider)
                 .setDeviceSurfaceManagerProvider(::CameraSurfaceAdapter)
                 .setUseCaseConfigFactoryProvider(::CameraUseCaseAdapter)
                 .build()
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
index fab238c..1aaab34 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
@@ -18,6 +18,7 @@
 import android.content.Context
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.core.Debug
 import androidx.camera.camera2.pipe.core.Log.debug
 import androidx.camera.camera2.pipe.core.SystemTimeSource
@@ -28,6 +29,7 @@
 import androidx.camera.camera2.pipe.integration.config.CameraAppConfig
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.config.DaggerCameraAppComponent
+import androidx.camera.camera2.pipe.integration.impl.CameraInteropStateCallbackRepository
 import androidx.camera.camera2.pipe.integration.internal.CameraCompatibilityFilter
 import androidx.camera.camera2.pipe.integration.internal.CameraSelectionOptimizer
 import androidx.camera.core.CameraInfo
@@ -43,24 +45,32 @@
  * to share resources across Camera instances.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-class CameraFactoryAdapter(
+internal class CameraFactoryAdapter(
+    lazyCameraPipe: Lazy<CameraPipe>,
     context: Context,
     threadConfig: CameraThreadConfig,
-    availableCamerasSelector: CameraSelector?
+    camera2InteropCallbacks: CameraInteropStateCallbackRepository,
+    availableCamerasSelector: CameraSelector?,
 ) : CameraFactory {
     private val appComponent: CameraAppComponent by lazy {
         Debug.traceStart { "CameraFactoryAdapter#appComponent" }
         val timeSource = SystemTimeSource()
         val start = Timestamps.now(timeSource)
         val result = DaggerCameraAppComponent.builder()
-            .config(CameraAppConfig(context, threadConfig))
+            .config(
+                CameraAppConfig(
+                    context,
+                    threadConfig,
+                    lazyCameraPipe.value,
+                    camera2InteropCallbacks
+                )
+            )
             .build()
         debug { "Created CameraFactoryAdapter in ${start.measureNow(timeSource).formatMs()}" }
         debug { "availableCamerasSelector: $availableCamerasSelector " }
         Debug.traceStop()
         result
     }
-
     private var mAvailableCamerasSelector: CameraSelector? = availableCamerasSelector
     private var mAvailableCameraIds: List<String>
 
@@ -124,5 +134,5 @@
         }
     }
 
-    override fun getCameraManager(): Any? = appComponent
+    override fun getCameraManager(): Any = appComponent
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt
new file mode 100644
index 0000000..91cc123
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.adapter
+
+import android.content.Context
+import androidx.annotation.GuardedBy
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.core.Debug
+import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.core.SystemTimeSource
+import androidx.camera.camera2.pipe.core.Timestamps
+import androidx.camera.camera2.pipe.core.Timestamps.formatMs
+import androidx.camera.camera2.pipe.core.Timestamps.measureNow
+import androidx.camera.camera2.pipe.integration.impl.CameraInteropStateCallbackRepository
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.impl.CameraFactory
+import androidx.camera.core.impl.CameraThreadConfig
+
+/**
+ * The [CameraFactoryProvider] is responsible for creating the root dagger component that is used
+ * to share resources across Camera instances.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class CameraFactoryProvider(
+    private val sharedCameraPipe: CameraPipe? = null,
+    private val sharedAppContext: Context? = null,
+    private val sharedThreadConfig: CameraThreadConfig? = null
+) : CameraFactory.Provider {
+    private val cameraInteropStateCallbackRepository = CameraInteropStateCallbackRepository()
+    private val lock = Any()
+
+    @GuardedBy("lock")
+    private var cachedCameraPipe: Pair<Context, Lazy<CameraPipe>>? = null
+
+    override fun newInstance(
+        context: Context,
+        threadConfig: CameraThreadConfig,
+        availableCamerasLimiter: CameraSelector?
+    ): CameraFactory {
+
+        val lazyCameraPipe = getOrCreateCameraPipe(context)
+
+        return CameraFactoryAdapter(
+            lazyCameraPipe,
+            sharedAppContext ?: context,
+            sharedThreadConfig ?: threadConfig,
+            cameraInteropStateCallbackRepository,
+            availableCamerasLimiter
+        )
+    }
+
+    private fun getOrCreateCameraPipe(context: Context): Lazy<CameraPipe> {
+        if (sharedCameraPipe != null) {
+            return lazyOf(sharedCameraPipe)
+        }
+
+        synchronized(lock) {
+            val existing = cachedCameraPipe
+            if (existing == null) {
+                val sharedCameraPipe = lazy { createCameraPipe(context) }
+                cachedCameraPipe = context to sharedCameraPipe
+                return sharedCameraPipe
+            } else {
+                check(context == existing.first) {
+                    "Mismatched context! Expected ${existing.first} but was $context"
+                }
+                return existing.second
+            }
+        }
+    }
+
+    private fun createCameraPipe(context: Context): CameraPipe {
+        Debug.traceStart { "Create CameraPipe" }
+        val timeSource = SystemTimeSource()
+        val start = Timestamps.now(timeSource)
+
+        val cameraPipe = CameraPipe(
+            CameraPipe.Config(
+                appContext = context.applicationContext,
+                cameraInteropConfig = CameraPipe.CameraInteropConfig(
+                    cameraInteropStateCallbackRepository.deviceStateCallback,
+                    cameraInteropStateCallbackRepository.sessionStateCallback
+                )
+            )
+        )
+        Log.debug { "Created CameraPipe in ${start.measureNow(timeSource).formatMs()}" }
+        Debug.traceStop()
+        return cameraPipe
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt
index 2f41d39..2ba8e67 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt
@@ -36,21 +36,6 @@
 )
 abstract class CameraAppModule {
     companion object {
-        @Singleton
-        @Provides
-        fun provideCameraPipe(
-            context: Context,
-            cameraInteropStateCallbackRepository: CameraInteropStateCallbackRepository
-        ): CameraPipe = CameraPipe(
-            CameraPipe.Config(
-                appContext = context.applicationContext,
-                cameraInteropConfig = CameraPipe.CameraInteropConfig(
-                    cameraInteropStateCallbackRepository.deviceStateCallback,
-                    cameraInteropStateCallbackRepository.sessionStateCallback
-                )
-            )
-        )
-
         @Provides
         fun provideCameraDevices(cameraPipe: CameraPipe): CameraDevices {
             return cameraPipe.cameras()
@@ -63,12 +48,21 @@
 class CameraAppConfig(
     private val context: Context,
     private val cameraThreadConfig: CameraThreadConfig,
+    private val cameraPipe: CameraPipe,
+    private val camera2InteropCallbacks: CameraInteropStateCallbackRepository
 ) {
     @Provides
     fun provideContext(): Context = context
 
     @Provides
     fun provideCameraThreadConfig(): CameraThreadConfig = cameraThreadConfig
+
+    @Provides
+    fun provideCameraPipe(): CameraPipe = cameraPipe
+
+    @Provides
+    fun provideCamera2InteropCallbacks(): CameraInteropStateCallbackRepository =
+        camera2InteropCallbacks
 }
 
 /** Dagger component for Application (Process) scoped dependencies. */
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraInteropStateCallbackRepository.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraInteropStateCallbackRepository.kt
index 179fcb0..02b7507 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraInteropStateCallbackRepository.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraInteropStateCallbackRepository.kt
@@ -24,20 +24,17 @@
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.core.impl.SessionConfig
-import javax.inject.Inject
-import javax.inject.Singleton
 import kotlinx.atomicfu.AtomicRef
 import kotlinx.atomicfu.atomic
 
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-@Singleton
 /**
  * A application-level single-instance repository for Camera Interop callbacks. It supplies
  * camera-pipe with internal callbacks on CameraX initialization. During runtime, before a camera
  * graph is created, CameraX updates these internal callbacks with Camera Interop callbacks so that
  * they may be triggered in camera-pipe.
  */
-class CameraInteropStateCallbackRepository @Inject constructor() {
+class CameraInteropStateCallbackRepository {
 
     private val _deviceStateCallback = CameraInteropDeviceStateCallback()
     private val _sessionStateCallback = CameraInteropSessionStateCallback()
@@ -61,7 +58,7 @@
     val sessionStateCallback
         get() = _sessionStateCallback
 
-    class CameraInteropDeviceStateCallback() : CameraDevice.StateCallback() {
+    class CameraInteropDeviceStateCallback : CameraDevice.StateCallback() {
 
         private var callbacks: AtomicRef<List<CameraDevice.StateCallback>> = atomic(listOf())
         internal fun updateCallbacks(sessionConfig: SessionConfig) {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraSelectionOptimizer.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraSelectionOptimizer.kt
index 35bc8dd..5179c88 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraSelectionOptimizer.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraSelectionOptimizer.kt
@@ -21,12 +21,12 @@
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.DoNotDisturbException
 import androidx.camera.camera2.pipe.core.Log
-import androidx.camera.camera2.pipe.integration.adapter.CameraFactoryAdapter
 import androidx.camera.camera2.pipe.integration.config.CameraAppComponent
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.InitializationException
+import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.CameraInfoInternal
 
 /**
@@ -34,12 +34,12 @@
  * passed CameraSelector
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-class CameraSelectionOptimizer {
+internal class CameraSelectionOptimizer {
     companion object {
 
         @Throws(InitializationException::class)
         fun getSelectedAvailableCameraIds(
-            cameraFactory: CameraFactoryAdapter,
+            cameraFactory: CameraFactory,
             availableCamerasSelector: CameraSelector?
         ): List<String> {
             try {
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraCompatibilityFilterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraCompatibilityFilterTest.kt
index 3b7478a..6af4773 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraCompatibilityFilterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraCompatibilityFilterTest.kt
@@ -23,7 +23,7 @@
 import android.os.Build
 import android.os.Handler
 import android.os.Looper
-import androidx.camera.camera2.pipe.integration.adapter.CameraFactoryAdapter
+import androidx.camera.camera2.pipe.integration.adapter.CameraFactoryProvider
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.impl.CameraThreadConfig
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
@@ -67,8 +67,8 @@
         ReflectionHelpers.setStaticField(Build::class.java, "FINGERPRINT", "fake-fingerprint")
 
         setupCameras()
-        val cameraFactoryAdapter = CameraFactoryAdapter(
-            ApplicationProvider.getApplicationContext<Context>(), CameraThreadConfig.create(
+        val cameraFactoryAdapter = CameraFactoryProvider().newInstance(
+            ApplicationProvider.getApplicationContext(), CameraThreadConfig.create(
                 CameraXExecutors.mainThreadExecutor(), Handler(Looper.getMainLooper())
             ), null
         )
@@ -84,7 +84,7 @@
 
         setupCameras()
 
-        val cameraFactoryAdapter = CameraFactoryAdapter(
+        val cameraFactoryAdapter = CameraFactoryProvider().newInstance(
             ApplicationProvider.getApplicationContext(), CameraThreadConfig.create(
                 CameraXExecutors.mainThreadExecutor(), Handler(Looper.getMainLooper())
             ), CameraSelector.DEFAULT_BACK_CAMERA
@@ -97,7 +97,7 @@
     fun NotFilterOutIncompatibleCameras_whenBuildFingerprintIsRobolectric() {
         setupCameras()
 
-        val cameraFactoryAdapter = CameraFactoryAdapter(
+        val cameraFactoryAdapter = CameraFactoryProvider().newInstance(
             ApplicationProvider.getApplicationContext(), CameraThreadConfig.create(
                 CameraXExecutors.mainThreadExecutor(), Handler(Looper.getMainLooper())
             ), null
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraSelectionOptimizerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraSelectionOptimizerTest.kt
index df10f33..f22f016 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraSelectionOptimizerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraSelectionOptimizerTest.kt
@@ -22,17 +22,17 @@
 import android.os.Build
 import android.os.Handler
 import android.os.Looper
-import androidx.camera.camera2.pipe.integration.adapter.CameraFactoryAdapter
+import androidx.camera.camera2.pipe.integration.adapter.CameraFactoryProvider
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraInfo
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.core.CameraFilter
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.CameraThreadConfig
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth
-import java.util.Arrays
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito
@@ -51,24 +51,16 @@
     instrumentedPackages = ["androidx.camera.camera2.pipe.integration.adapter"]
 )
 class CameraSelectionOptimizerTest {
+    private lateinit var cameraFactory: CameraFactory
 
-    private var mCameraFactoryAdapter: CameraFactoryAdapter = CameraFactoryAdapter(
-        ApplicationProvider.getApplicationContext<Context>(),
-        CameraThreadConfig.create(
-            CameraXExecutors.mainThreadExecutor(),
-            Handler(Looper.getMainLooper())
-        ),
-        null
-    )
-
-    fun setupNormalCameras() {
+    private fun setupNormalCameras() {
         initCharacteristics("0", CameraCharacteristics.LENS_FACING_BACK, 3.52f)
         initCharacteristics("1", CameraCharacteristics.LENS_FACING_FRONT, 3.52f)
         initCharacteristics("2", CameraCharacteristics.LENS_FACING_BACK, 2.7f)
         initCharacteristics("3", CameraCharacteristics.LENS_FACING_BACK, 10.0f)
     }
 
-    fun setupAbnormalCameras() {
+    private fun setupAbnormalCameras() {
         // "0" is front
         initCharacteristics("0", CameraCharacteristics.LENS_FACING_FRONT, 3.52f)
         // "1" is back
@@ -96,7 +88,7 @@
         val cameraIds: List<String> = getCameraIdsBasedOnCameraSelector(cameraSelector)
 
         Truth.assertThat(cameraIds).containsExactly("0", "2", "3")
-        Mockito.verify<CameraFactoryAdapter>(mCameraFactoryAdapter, Mockito.never())
+        Mockito.verify(cameraFactory, Mockito.never())
             .getCamera("1")
     }
 
@@ -111,7 +103,7 @@
 
         val cameraIds: List<String> = getCameraIdsBasedOnCameraSelector(cameraSelector)
         Truth.assertThat(cameraIds).containsExactly("1")
-        Mockito.verify<CameraFactoryAdapter>(mCameraFactoryAdapter, Mockito.never())
+        Mockito.verify(cameraFactory, Mockito.never())
             .getCamera("0")
     }
 
@@ -134,7 +126,7 @@
                     minFocalCameraInfo = cameraInfo
                 }
             }
-            Arrays.asList(minFocalCameraInfo)
+            listOf(minFocalCameraInfo)
         }
         val cameraSelector = CameraSelector.Builder()
             .requireLensFacing(CameraSelector.LENS_FACING_BACK)
@@ -143,7 +135,7 @@
         val cameraIds: List<String> = getCameraIdsBasedOnCameraSelector(cameraSelector)
         Truth.assertThat(cameraIds).containsExactly("2")
         // only camera "1" 's getCameraCharacteristics can be avoided.
-        Mockito.verify<CameraFactoryAdapter>(mCameraFactoryAdapter, Mockito.never())
+        Mockito.verify(cameraFactory, Mockito.never())
             .getCamera("1")
     }
 
@@ -201,24 +193,19 @@
     }
 
     private fun getCameraIdsBasedOnCameraSelector(cameraSelector: CameraSelector?): List<String> {
-
-        mCameraFactoryAdapter = Mockito.spy(
-            CameraFactoryAdapter(
-                ApplicationProvider.getApplicationContext<Context>(),
-                CameraThreadConfig.create(
-                    CameraXExecutors.mainThreadExecutor(),
-                    Handler(Looper.getMainLooper())
-                ),
-                cameraSelector
-            )
+        val actualCameraFactory = CameraFactoryProvider().newInstance(
+            ApplicationProvider.getApplicationContext(), CameraThreadConfig.create(
+                CameraXExecutors.mainThreadExecutor(), Handler(Looper.getMainLooper())
+            ),
+            cameraSelector
         )
 
-        val cameraIds: List<String> =
-            CameraSelectionOptimizer.getSelectedAvailableCameraIds(
-                mCameraFactoryAdapter,
-                cameraSelector
-            )
-        return cameraIds
+        cameraFactory = Mockito.spy(actualCameraFactory)
+
+        return CameraSelectionOptimizer.getSelectedAvailableCameraIds(
+            cameraFactory,
+            cameraSelector
+        )
     }
 
     private fun initCharacteristics(cameraId: String, lensFacing: Int, focalLength: Float) {
diff --git a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/MainActivity.kt b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/MainActivity.kt
index c7a330c..4d86552 100644
--- a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/MainActivity.kt
+++ b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/MainActivity.kt
@@ -16,9 +16,13 @@
 
 package androidx.camera.integration.avsync
 
+import android.os.Build
 import android.os.Bundle
+import android.view.WindowManager
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
 import androidx.compose.material.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.core.util.Preconditions
@@ -36,12 +40,30 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        handleScreenLock()
         setScreenBrightness()
         setContent {
             App(getBeepFrequency(), getBeepEnabled())
         }
     }
 
+    private fun handleScreenLock() {
+        if (Build.VERSION.SDK_INT >= 27) {
+            Api27Impl.setShowWhenLocked(this, true)
+            Api27Impl.setTurnScreenOn(this, true)
+            window.addFlags(
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+            )
+        } else {
+            @Suppress("DEPRECATION")
+            window.addFlags(
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                    or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                    or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+            )
+        }
+    }
+
     private fun getBeepFrequency(): Int {
         val frequency = intent.getStringExtra(KEY_BEEP_FREQUENCY)
 
@@ -67,6 +89,17 @@
         layoutParam.screenBrightness = brightness
         window.attributes = layoutParam
     }
+
+    @RequiresApi(27)
+    private object Api27Impl {
+        @DoNotInline
+        fun setShowWhenLocked(activity: ComponentActivity, showWhenLocked: Boolean) =
+            activity.setShowWhenLocked(showWhenLocked)
+
+        @DoNotInline
+        fun setTurnScreenOn(activity: ComponentActivity, turnScreenOn: Boolean) =
+            activity.setTurnScreenOn(turnScreenOn)
+    }
 }
 
 @Composable
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
index fd2dde3..4de2e3a 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
@@ -134,10 +134,18 @@
                     // SystemUiVisibility set in CarAppActivity#onCreate(). Failing to do so would
                     // cause a mismatch between the insets applied to the content on the hosts side
                     // vs. the actual visible window available on the client side.
-                    Insets insets = WindowInsetsCompat.toWindowInsetsCompat(windowInsets)
-                            .getInsets(WindowInsetsCompat.Type.systemBars()
-                                    | WindowInsetsCompat.Type.ime())
-                            .toPlatformInsets();
+                    Insets insets;
+                    // Android U+ (SDK 34+) introduced SYSTEM_OVERLAYS insets, which we pass to the
+                    // host to adjust padding.
+                    if (Build.VERSION.SDK_INT >= 34) {
+                        // TODO(b/287700349): Add tests once Robolectric supports SDK 34.
+                        insets = Api34Impl.getInsets(windowInsets);
+                    } else {
+                        insets = WindowInsetsCompat.toWindowInsetsCompat(windowInsets)
+                                .getInsets(WindowInsetsCompat.Type.systemBars()
+                                        | WindowInsetsCompat.Type.ime())
+                                .toPlatformInsets();
+                    }
                     DisplayCutoutCompat displayCutout =
                             WindowInsetsCompat.toWindowInsetsCompat(windowInsets)
                                     .getDisplayCutout();
@@ -244,6 +252,18 @@
                 }
             };
 
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static class Api34Impl {
+
+        private Api34Impl() {
+        }
+
+        static Insets getInsets(WindowInsets windowInsets) {
+            return windowInsets.getInsets(WindowInsets.Type.systemBars() | WindowInsets.Type.ime()
+                    | WindowInsets.Type.systemOverlays());
+        }
+    }
+
     @RequiresApi(Build.VERSION_CODES.R)
     private static class Api30Impl {
         private Api30Impl() {
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
index d9d3573..f01eced 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
@@ -245,8 +245,8 @@
     <string name="email_hint" msgid="7205549445477319606">"ଇମେଲ"</string>
     <string name="sign_in_title" msgid="4551967308262681703">"ସାଇନ ଇନ କରନ୍ତୁ"</string>
     <string name="sign_in_instructions" msgid="9044850228284294762">"ଆପଣଙ୍କ କ୍ରେଡେନ୍ସିଆଲ ଲେଖନ୍ତୁ"</string>
-    <string name="invalid_email_error_msg" msgid="5261362663718987167">"ଉପଯୋଗକର୍ତ୍ତା ନାମ ଏକ ବୈଧ ଇମେଲ ଠିକଣା ହୋଇଥିବା ଆବଶ୍ୟକ"</string>
-    <string name="invalid_length_error_msg" msgid="8238905276326976425">"ଉପଯୋଗକର୍ତ୍ତା ନାମରେ ଅତି କମରେ %sଟି କେରେକ୍ଟର ରହିବା ଆବଶ୍ୟକ"</string>
+    <string name="invalid_email_error_msg" msgid="5261362663718987167">"ୟୁଜର ନେମ ଏକ ବୈଧ ଇମେଲ ଠିକଣା ହୋଇଥିବା ଆବଶ୍ୟକ"</string>
+    <string name="invalid_length_error_msg" msgid="8238905276326976425">"ୟୁଜର ନେମରେ ଅତି କମରେ %sଟି କେରେକ୍ଟର ରହିବା ଆବଶ୍ୟକ"</string>
     <string name="invalid_password_error_msg" msgid="1090359893902674610">"ଅବୈଧ ପାସୱାର୍ଡ"</string>
     <string name="password_hint" msgid="2869107073860012864">"ପାସୱାର୍ଡ"</string>
     <string name="password_sign_in_instruction_prefix" msgid="9105788349198243508">"ଉପଯୋଗକର୍ତ୍ତାନାମ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
index 7c4bf36..1c6bdb6 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
@@ -358,7 +358,7 @@
     <string name="loading_toggle_enabled" msgid="8828072732804454994">"Carregamento ativado"</string>
     <string name="loading_toggle_disabled" msgid="7689738885077382673">"Carregamento desativado"</string>
     <string name="loading_screen" msgid="4771507490730308794">"Tela de carregamento"</string>
-    <string name="vector_toggle_details" msgid="1301305340033556819">"Alternar para adicionar/remover cor"</string>
+    <string name="vector_toggle_details" msgid="1301305340033556819">"Mudar para adicionar/remover cor"</string>
     <string name="map_template_toggle_demo_title" msgid="6510798293640092611">"Modelo de mapa com botões"</string>
     <string name="avoid_tolls_row_title" msgid="5194057244144831024">"Evitar pedágios"</string>
     <string name="route_options_demo_title" msgid="4599699012716426514">"Opções de trajeto"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml
index 7c4bf36..1c6bdb6 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml
@@ -358,7 +358,7 @@
     <string name="loading_toggle_enabled" msgid="8828072732804454994">"Carregamento ativado"</string>
     <string name="loading_toggle_disabled" msgid="7689738885077382673">"Carregamento desativado"</string>
     <string name="loading_screen" msgid="4771507490730308794">"Tela de carregamento"</string>
-    <string name="vector_toggle_details" msgid="1301305340033556819">"Alternar para adicionar/remover cor"</string>
+    <string name="vector_toggle_details" msgid="1301305340033556819">"Mudar para adicionar/remover cor"</string>
     <string name="map_template_toggle_demo_title" msgid="6510798293640092611">"Modelo de mapa com botões"</string>
     <string name="avoid_tolls_row_title" msgid="5194057244144831024">"Evitar pedágios"</string>
     <string name="route_options_demo_title" msgid="4599699012716426514">"Opções de trajeto"</string>
diff --git a/collection/collection/build.gradle b/collection/collection/build.gradle
index b69575e..afab27a 100644
--- a/collection/collection/build.gradle
+++ b/collection/collection/build.gradle
@@ -48,7 +48,7 @@
         commonMain {
             dependencies {
                 api(libs.kotlinStdlib)
-                api(project(":annotation:annotation"))
+                api(projectOrArtifact(":annotation:annotation"))
             }
         }
 
diff --git a/compose/desktop/desktop/OWNERS b/compose/desktop/desktop/OWNERS
index a775e737..527b6c2 100644
--- a/compose/desktop/desktop/OWNERS
+++ b/compose/desktop/desktop/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 378604
 jsproch@google.com
 andreykulikov@google.com
 lpf@google.com
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt
index b0e09b9..f82f6f9 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2FilterDemos.kt
@@ -20,8 +20,10 @@
 
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.demos.text.TagLine
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.samples.BasicTextField2ChangeIterationSample
@@ -36,8 +38,13 @@
 import androidx.compose.foundation.text2.input.allCaps
 import androidx.compose.foundation.text2.input.maxLengthInChars
 import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Switch
+import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.intl.Locale
@@ -57,7 +64,10 @@
         FilterDemo(filter = TextEditFilter.maxLengthInChars(5))
 
         TagLine(tag = "Digits Only BasicTextField2")
-        DigitsOnlyBasicTextField2()
+        DigitsOnlyDemo()
+
+        TagLine(tag = "Change filter")
+        ChangeFilterDemo()
 
         TagLine(tag = "Custom (type backwards with prompt)")
         Box(demoTextFieldModifiers, propagateMinConstraints = true) {
@@ -78,7 +88,7 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-fun DigitsOnlyBasicTextField2() {
+private fun DigitsOnlyDemo() {
     FilterDemo(filter = object : TextEditFilter {
         override val keyboardOptions = KeyboardOptions(
             keyboardType = KeyboardType.Number
@@ -103,4 +113,24 @@
         filter = filter,
         modifier = demoTextFieldModifiers
     )
+}
+
+@Composable
+private fun ChangeFilterDemo() {
+    var filter: TextEditFilter? by remember { mutableStateOf(null) }
+    val state = remember { TextFieldState() }
+
+    Column {
+        Row(horizontalArrangement = Arrangement.SpaceBetween) {
+            Text("Filter enabled?")
+            Switch(checked = filter != null, onCheckedChange = {
+                filter = if (filter == null) TextEditFilter.allCaps(Locale.current) else null
+            })
+        }
+        BasicTextField2(
+            state = state,
+            filter = filter,
+            modifier = demoTextFieldModifiers
+        )
+    }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputAdapterTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputAdapterTest.kt
index 2c65d65..a0ce00020 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputAdapterTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputAdapterTest.kt
@@ -38,7 +38,6 @@
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertFalse
@@ -281,38 +280,6 @@
     }
 
     @Test
-    fun showSoftwareKeyboard_fromActiveInputSession_showsTheKeyboard() {
-        var session: TextInputSession? = null
-
-        rule.runOnUiThread {
-            session = adapter.startInputSessionWithDefaultsForTest()
-        }
-
-        keyboardHelper.hideKeyboardIfShown()
-        keyboardHelper.waitForKeyboardVisibility(false)
-
-        session?.showSoftwareKeyboard()
-        keyboardHelper.waitForKeyboardVisibility(true)
-        assertThat(keyboardHelper.isSoftwareKeyboardShown()).isTrue()
-    }
-
-    @SdkSuppress(minSdkVersion = 23)
-    @Test
-    fun hideSoftwareKeyboard_fromActiveInputSession_hidesTheKeyboard() {
-        var session: TextInputSession? = null
-
-        rule.runOnUiThread {
-            session = adapter.startInputSessionWithDefaultsForTest()
-        }
-
-        keyboardHelper.waitForKeyboardVisibility(true)
-
-        session?.hideSoftwareKeyboard()
-        keyboardHelper.waitForKeyboardVisibility(false)
-        assertThat(keyboardHelper.isSoftwareKeyboardShown()).isFalse()
-    }
-
-    @Test
     fun debugMode_isDisabled() {
         // run this in presubmit to check that we are not accidentally enabling logs on prod
         assertFalse(
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnectionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnectionTest.kt
index e04e539..31f9da4 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnectionTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnectionTest.kt
@@ -19,7 +19,6 @@
 import android.view.KeyEvent
 import android.view.inputmethod.EditorInfo
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.TextEditFilter
 import androidx.compose.foundation.text2.input.TextFieldCharSequence
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.text.TextRange
@@ -68,18 +67,6 @@
             override val imeOptions: ImeOptions
                 get() = this@StatelessInputConnectionTest.imeOptions
 
-            override fun setFilter(filter: TextEditFilter?) {
-                // Noop.
-            }
-
-            override fun showSoftwareKeyboard() {
-                // noop
-            }
-
-            override fun hideSoftwareKeyboard() {
-                // noop
-            }
-
             override fun onImeAction(imeAction: ImeAction) {
                 this@StatelessInputConnectionTest.onImeAction?.invoke(imeAction)
             }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldCursorHandleTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldCursorHandleTest.kt
index ef8fab9..f3b3bf9 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldCursorHandleTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldCursorHandleTest.kt
@@ -104,6 +104,29 @@
     }
 
     @Test
+    fun tapTextField_cursorHandleFiltered() = with(rule.density) {
+        state = TextFieldState("hello")
+        rule.setContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                filter = { _, valueWithChanges ->
+                    valueWithChanges.selectCharsIn(TextRange(4))
+                },
+                modifier = Modifier.testTag(TAG)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
+        }
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+    }
+
+    @Test
     fun cursorHandle_showsAtCorrectLocation_outOfTextBoundsTouch_ltr() = with(rule.density) {
         state = TextFieldState("hello")
         rule.setContent {
@@ -479,6 +502,33 @@
         rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
     }
 
+    @Test
+    fun cursorHandleDrag_getsFiltered() {
+        state = TextFieldState("abc abc")
+        rule.setContent {
+            BasicTextField2(
+                state,
+                textStyle = TextStyle(fontSize = fontSize, fontFamily = TEST_FONT_FAMILY),
+                filter = { _, valueWithChanges ->
+                    valueWithChanges.selectCharsIn(TextRange.Zero)
+                },
+                modifier = Modifier
+                    .testTag(TAG)
+                    .width(with(rule.density) { fontSize.toDp() } * 10)
+            )
+        }
+
+        focusAndWait()
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(1f, 1f)) } // click most left
+        rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
+
+        swipeToRight(fontSizePx * 5)
+        rule.waitForIdle()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange.Zero)
+    }
+
     // region ltr drag tests
     @Test
     fun moveCursorHandleToRight_ltr() {
@@ -710,10 +760,12 @@
 
     @Test
     fun moveCursorHandleToLeft_rtl_outOfBounds_scrollable_continuesDrag() {
-        state = TextFieldState("\u05D0\u05D1\u05D2\u05D3 " +
+        state = TextFieldState(
             "\u05D0\u05D1\u05D2\u05D3 " +
-            "\u05D0\u05D1\u05D2\u05D3 " +
-            "\u05D0\u05D1\u05D2\u05D3")
+                "\u05D0\u05D1\u05D2\u05D3 " +
+                "\u05D0\u05D1\u05D2\u05D3 " +
+                "\u05D0\u05D1\u05D2\u05D3"
+        )
         rule.setContent {
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                 BasicTextField2(
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldTextToolbarTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldTextToolbarTest.kt
new file mode 100644
index 0000000..3ba9950
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/selection/TextFieldTextToolbarTest.kt
@@ -0,0 +1,548 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2.selection
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.selection.FakeTextToolbar
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.foundation.text2.BasicTextField2
+import androidx.compose.foundation.text2.input.TextFieldLineLimits
+import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text2.input.placeCursorAtEnd
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.platform.TextToolbarStatus
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performMouseInput
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Fact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+class TextFieldTextToolbarTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    val fontSize = 10.sp
+
+    val fontSizePx = with(rule.density) { fontSize.toPx() }
+
+    val TAG = "BasicTextField2"
+
+    @Test
+    fun toolbarAppears_whenCursorHandleIsClicked() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+    }
+
+    @Test
+    fun toolbarDisappears_whenCursorHandleIsClickedAgain() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+    }
+
+    @Test
+    fun toolbarDisappears_whenTextStateIsUpdated() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+
+        state.edit {
+            append(" World!")
+            placeCursorAtEnd()
+        }
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarDisappears_whenTextIsEntered_throughIME() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+
+        rule.onNodeWithTag(TAG).performTextInput(" World!")
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun toolbarDisappears_whenTextIsEntered_throughHardwareKeyboard() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+
+        rule.onNodeWithTag(TAG).performKeyInput {
+            pressKey(Key.W)
+        }
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarTemporarilyHides_whenHandleIsBeingDragged() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(0f, fontSizePx / 2)) }
+
+        with(rule.onNode(isSelectionHandle(Handle.Cursor))) {
+            performClick()
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+            performTouchInput {
+                down(center)
+                moveBy(Offset(viewConfiguration.touchSlop, 0f))
+                moveBy(Offset(fontSizePx, 0f))
+            }
+        }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performTouchInput {
+            up()
+        }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+    }
+
+    @Test
+    fun toolbarTemporarilyHides_whenCursor_goesOutOfBounds() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello ".repeat(20)) // make sure the field is scrollable
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { swipeLeft(startX = fontSizePx * 3, endX = 0f) }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { swipeRight(startX = 0f, endX = fontSizePx * 3) }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+        }
+    }
+
+    @Test
+    fun toolbarFollowsTheCursor_whenTextFieldIsScrolled() {
+        var shownRect: Rect? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { rect, _, _, _, _ ->
+                shownRect = rect
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello ".repeat(20)) // make sure the field is scrollable
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        lateinit var firstRectAnchor: Rect
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+            firstRectAnchor = shownRect!!
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput {
+            down(center)
+            moveBy(Offset(-viewConfiguration.touchSlop - fontSizePx, 0f))
+            up()
+        }
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+            val secondRectAnchor = shownRect!!
+            Truth.assertAbout(RectSubject.SUBJECT_FACTORY)
+                .that(secondRectAnchor)!!
+                .isEqualToWithTolerance(
+                    firstRectAnchor.translate(
+                        translateX = -fontSizePx,
+                        translateY = 0f
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun toolbarShowsSelectAll() {
+        var selectAllOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, _, _, onSelectAllRequested ->
+                selectAllOptionAvailable = onSelectAllRequested != null
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(selectAllOptionAvailable).isTrue()
+        }
+    }
+
+    @Test
+    fun toolbarDoesNotShowSelectAll_whenAllTextIsAlreadySelected() {
+        var selectAllOption: (() -> Unit)? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, _, _, onSelectAllRequested ->
+                selectAllOption = onSelectAllRequested
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(selectAllOption).isNotNull()
+        }
+
+        selectAllOption?.invoke()
+
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+        rule.runOnIdle {
+            assertThat(selectAllOption).isNull()
+        }
+    }
+
+    @Test
+    fun toolbarDoesNotShowPaste_whenClipboardHasNoContent() {
+        var pasteOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, onPasteRequested, _, _ ->
+                pasteOptionAvailable = onPasteRequested != null
+            },
+            onHideMenu = {}
+        )
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(pasteOptionAvailable).isFalse()
+        }
+    }
+
+    @Test
+    fun toolbarShowsPaste_whenClipboardHasContent() {
+        var pasteOptionAvailable = false
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, onPasteRequested, _, _ ->
+                pasteOptionAvailable = onPasteRequested != null
+            },
+            onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager("world")
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true, clipboardManager)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click() }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            assertThat(pasteOptionAvailable).isTrue()
+        }
+    }
+
+    @Test
+    fun pasteInsertsContentAtCursor_placesCursorAfterInsertedContent() {
+        var pasteOption: (() -> Unit)? = null
+        val textToolbar = FakeTextToolbar(
+            onShowMenu = { _, _, onPasteRequested, _, _ ->
+                pasteOption = onPasteRequested
+            },
+            onHideMenu = {}
+        )
+        val clipboardManager = FakeClipboardManager("world")
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar, true, clipboardManager)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, 0f)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+
+        rule.runOnIdle {
+            pasteOption!!.invoke()
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("Heworldllo")
+            assertThat(state.text.selectionInChars).isEqualTo(TextRange(7))
+        }
+    }
+
+    @Test
+    fun tappingTextField_hidesTheToolbar() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun interactingWithTextFieldByMouse_doeNotShowTheToolbar() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        setupContent(state, textToolbar)
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performMouseInput {
+            click()
+        }
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+    }
+
+    @Test
+    fun toolbarDisappears_whenFocusIsLost() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        val focusRequester = FocusRequester()
+        rule.setContent {
+            CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
+                Column {
+                    Box(
+                        modifier = Modifier
+                            .focusRequester(focusRequester)
+                            .focusable()
+                            .size(100.dp)
+                    )
+                    BasicTextField2(
+                        state = state,
+                        modifier = Modifier
+                            .width(100.dp)
+                            .testTag(TAG),
+                        textStyle = TextStyle(
+                            fontFamily = TEST_FONT_FAMILY,
+                            fontSize = fontSize
+                        )
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    @Test
+    fun toolbarDisappears_whenTextFieldIsDisposed() {
+        val textToolbar = FakeTextToolbar()
+        val state = TextFieldState("Hello")
+        val toggleState = mutableStateOf(true)
+        rule.setContent {
+            CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
+                Column {
+                    if (toggleState.value) {
+                        BasicTextField2(
+                            state = state,
+                            modifier = Modifier
+                                .width(100.dp)
+                                .testTag(TAG),
+                            textStyle = TextStyle(
+                                fontFamily = TEST_FONT_FAMILY,
+                                fontSize = fontSize
+                            )
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag(TAG).performTouchInput { click(Offset(fontSizePx * 2, fontSizePx / 2)) }
+        rule.onNode(isSelectionHandle(Handle.Cursor)).performClick()
+        assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
+
+        toggleState.value = false
+
+        rule.runOnIdle {
+            assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
+        }
+    }
+
+    private fun setupContent(
+        state: TextFieldState = TextFieldState(),
+        toolbar: TextToolbar = FakeTextToolbar(),
+        singleLine: Boolean = false,
+        clipboardManager: ClipboardManager = FakeClipboardManager(),
+    ) {
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalTextToolbar provides toolbar,
+                LocalClipboardManager provides clipboardManager
+            ) {
+                BasicTextField2(
+                    state = state,
+                    modifier = Modifier
+                        .width(100.dp)
+                        .testTag(TAG),
+                    textStyle = TextStyle(
+                        fontFamily = TEST_FONT_FAMILY,
+                        fontSize = fontSize
+                    ),
+                    lineLimits = if (singleLine) {
+                        TextFieldLineLimits.SingleLine
+                    } else {
+                        TextFieldLineLimits.Default
+                    }
+                )
+            }
+        }
+    }
+
+    private fun FakeTextToolbar() = FakeTextToolbar(
+        onShowMenu = { _, _, _, _, _ -> },
+        onHideMenu = {
+            println("hide")
+        }
+    )
+}
+
+internal class RectSubject private constructor(
+    failureMetadata: FailureMetadata?,
+    private val subject: Rect?
+) : Subject(failureMetadata, subject) {
+
+    companion object {
+        internal val SUBJECT_FACTORY: Factory<RectSubject?, Rect?> =
+            Factory { failureMetadata, subject -> RectSubject(failureMetadata, subject) }
+    }
+
+    fun isEqualToWithTolerance(expected: Rect, tolerance: Float = 1f) {
+        if (subject == null) failWithoutActual(Fact.simpleFact("is null"))
+        check("instanceOf()").that(subject).isInstanceOf(Rect::class.java)
+        assertThat(subject!!.left).isWithin(tolerance).of(expected.left)
+        assertThat(subject.top).isWithin(tolerance).of(expected.top)
+        assertThat(subject.right).isWithin(tolerance).of(expected.right)
+        assertThat(subject.bottom).isWithin(tolerance).of(expected.bottom)
+    }
+}
+
+internal fun FakeClipboardManager(
+    initialText: String? = null
+) = object : ClipboardManager {
+    private var currentText: AnnotatedString? = initialText?.let { AnnotatedString(it) }
+    override fun setText(annotatedString: AnnotatedString) {
+        currentText = annotatedString
+    }
+
+    override fun getText(): AnnotatedString? {
+        return currentText
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
index fbcc86f..b6a7fd2 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
@@ -32,7 +32,6 @@
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.text.heightInLines
-import androidx.compose.foundation.text.isInTouchMode
 import androidx.compose.foundation.text.selection.SelectionHandleInfo
 import androidx.compose.foundation.text.selection.SelectionHandleInfoKey
 import androidx.compose.foundation.text.textFieldMinSize
@@ -49,6 +48,7 @@
 import androidx.compose.foundation.text2.input.internal.TextLayoutState
 import androidx.compose.foundation.text2.selection.TextFieldSelectionState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clipToBounds
@@ -56,10 +56,12 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.LocalPlatformTextInputPluginRegistry
+import androidx.compose.ui.platform.LocalTextToolbar
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextStyle
@@ -162,10 +164,27 @@
 
     val textLayoutState = remember { TextLayoutState() }
 
-    val textFieldSelectionState = remember(state, textLayoutState, density) {
-        TextFieldSelectionState(state, textLayoutState, density)
+    val textFieldSelectionState = remember(state, textLayoutState) {
+        TextFieldSelectionState(
+            textFieldState = state,
+            textLayoutState = textLayoutState,
+            textEditFilter = filter,
+            density = density,
+            editable = enabled && !readOnly
+        )
     }
     textFieldSelectionState.hapticFeedBack = LocalHapticFeedback.current
+    textFieldSelectionState.clipboardManager = LocalClipboardManager.current
+    textFieldSelectionState.textToolbar = LocalTextToolbar.current
+    textFieldSelectionState.textEditFilter = filter
+    textFieldSelectionState.density = density
+    textFieldSelectionState.editable = enabled && !readOnly
+
+    DisposableEffect(textFieldSelectionState) {
+        onDispose {
+            textFieldSelectionState.dispose()
+        }
+    }
 
     val decorationModifiers = modifier
         .then(
@@ -267,7 +286,7 @@
                     )
                 }
                 .pointerInput(selectionState) {
-                    with(selectionState) { detectCursorHandleDragGestures() }
+                    with(selectionState) { cursorHandleGestures() }
                 },
             content = null
         )
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt
index e8b4877..5081777 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/TextFieldCharSequence.kt
@@ -122,4 +122,35 @@
         result = 31 * result + (compositionInChars?.hashCode() ?: 0)
         return result
     }
-}
\ No newline at end of file
+}
+
+/**
+ * Returns the text before the selection.
+ *
+ * @param maxChars maximum number of characters (inclusive) before the minimum value in
+ * [TextFieldCharSequence.selectionInChars].
+ *
+ * @see TextRange.min
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldCharSequence.getTextBeforeSelection(maxChars: Int): CharSequence =
+    subSequence(kotlin.math.max(0, selectionInChars.min - maxChars), selectionInChars.min)
+
+/**
+ * Returns the text after the selection.
+ *
+ * @param maxChars maximum number of characters (exclusive) after the maximum value in
+ * [TextFieldCharSequence.selectionInChars].
+ *
+ * @see TextRange.max
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldCharSequence.getTextAfterSelection(maxChars: Int): CharSequence =
+    subSequence(selectionInChars.max, kotlin.math.min(selectionInChars.max + maxChars, length))
+
+/**
+ * Returns the currently selected text.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldCharSequence.getSelectedText(): CharSequence =
+    subSequence(selectionInChars.min, selectionInChars.max)
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt
index 003c41d7..9417a76 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt
@@ -21,7 +21,6 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text2.input.internal.EditProcessor
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SnapshotMutationPolicy
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.saveable.SaverScope
 import androidx.compose.runtime.saveable.rememberSaveable
@@ -259,10 +258,3 @@
         }
     }
 }
-
-@OptIn(ExperimentalFoundationApi::class)
-internal val TextOnlyMutationPolicy = object : SnapshotMutationPolicy<TextFieldCharSequence> {
-    override fun equivalent(a: TextFieldCharSequence, b: TextFieldCharSequence): Boolean {
-        return a.contentEquals(b)
-    }
-}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputAdapter.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputAdapter.kt
index 8f99bcc..4aad0a2 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputAdapter.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputAdapter.kt
@@ -119,15 +119,14 @@
      *
      * @param state Text editing state
      * @param imeOptions How to configure the IME when creating new [InputConnection]s
-     * @param initialFilter The initial [TextEditFilter]. The filter can be changed after the
-     * session is started by calling [TextInputSession.setFilter].
+     * @param filter The [TextEditFilter].
      * @param onImeActionPerformed A callback to pass received editor action from IME.
      * @return A handle to manage active session between Adapter and platform APIs.
      */
     fun startInputSession(
         state: TextFieldState,
         imeOptions: ImeOptions,
-        initialFilter: TextEditFilter?,
+        filter: TextEditFilter?,
         onImeActionPerformed: (ImeAction) -> Unit
     ): TextInputSession {
         if (!isMainThread()) {
@@ -140,7 +139,7 @@
         val nextSession = createEditableTextInputSession(
             state = state,
             imeOptions = imeOptions,
-            initialFilter = initialFilter,
+            filter = filter,
             onImeActionPerformed = onImeActionPerformed
         )
         currentTextInputSession = nextSession
@@ -150,7 +149,7 @@
     private fun createEditableTextInputSession(
         state: TextFieldState,
         imeOptions: ImeOptions,
-        initialFilter: TextEditFilter?,
+        filter: TextEditFilter?,
         onImeActionPerformed: (ImeAction) -> Unit
     ) = object : EditableTextInputSession {
 
@@ -163,18 +162,6 @@
         override val isOpen: Boolean
             get() = currentTextInputSession == this
 
-        override fun showSoftwareKeyboard() {
-            if (isOpen) {
-                textInputCommandExecutor.send(TextInputCommand.ShowKeyboard)
-            }
-        }
-
-        override fun hideSoftwareKeyboard() {
-            if (isOpen) {
-                textInputCommandExecutor.send(TextInputCommand.HideKeyboard)
-            }
-        }
-
         override fun dispose() {
             state.editProcessor.removeResetListener(resetListener)
             stopInputSession(this)
@@ -185,12 +172,6 @@
         override val value: TextFieldCharSequence
             get() = state.text
 
-        private var filter: TextEditFilter? = initialFilter
-
-        override fun setFilter(filter: TextEditFilter?) {
-            this.filter = filter
-        }
-
         override fun requestEdits(editCommands: List<EditCommand>) {
             state.editProcessor.update(editCommands, filter)
         }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnection.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnection.kt
index c2e492c..f55668a 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnection.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/StatelessInputConnection.kt
@@ -31,11 +31,11 @@
 import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.ui.text.TextRange
+import androidx.compose.foundation.text2.input.getSelectedText
+import androidx.compose.foundation.text2.input.getTextAfterSelection
+import androidx.compose.foundation.text2.input.getTextBeforeSelection
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.TextFieldValue
-import kotlin.math.max
-import kotlin.math.min
 
 @VisibleForTesting
 internal const val SIC_DEBUG = false
@@ -365,34 +365,6 @@
 
     // endregion
 
-    /**
-     * Returns the text before the selection.
-     *
-     * @param maxChars maximum number of characters (inclusive) before the minimum value in
-     * [TextFieldCharSequence.selectionInChars].
-     *
-     * @see TextRange.min
-     */
-    fun TextFieldCharSequence.getTextBeforeSelection(maxChars: Int): CharSequence =
-        subSequence(max(0, selectionInChars.min - maxChars), selectionInChars.min)
-
-    /**
-     * Returns the text after the selection.
-     *
-     * @param maxChars maximum number of characters (exclusive) after the maximum value in
-     * [TextFieldCharSequence.selectionInChars].
-     *
-     * @see TextRange.max
-     */
-    fun TextFieldCharSequence.getTextAfterSelection(maxChars: Int): CharSequence =
-        subSequence(selectionInChars.max, min(selectionInChars.max + maxChars, length))
-
-    /**
-     * Returns the currently selected text.
-     */
-    fun TextFieldCharSequence.getSelectedText(): CharSequence =
-        subSequence(selectionInChars.min, selectionInChars.max)
-
     private fun logDebug(message: String) {
         if (SIC_DEBUG) {
             Log.d(TAG, "$DEBUG_CLASS.$message, $isICActive, ${activeSessionProvider() != null}")
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt
index 4737187..4adddef 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt
@@ -21,13 +21,11 @@
 import androidx.compose.foundation.text.KeyboardActionScope
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text.selection.isPrecisePointer
 import androidx.compose.foundation.text2.BasicTextField2
 import androidx.compose.foundation.text2.input.TextEditFilter
 import androidx.compose.foundation.text2.input.TextFieldCharSequence
 import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.deselect
-import androidx.compose.foundation.text2.input.selectCharsIn
 import androidx.compose.foundation.text2.selection.TextFieldSelectionState
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusEventModifierNode
@@ -50,6 +48,8 @@
 import androidx.compose.ui.node.currentValueOf
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.platform.SoftwareKeyboardController
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.semantics.disabled
 import androidx.compose.ui.semantics.editableText
@@ -69,6 +69,7 @@
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 /**
@@ -147,34 +148,23 @@
     CompositionLocalConsumerModifierNode {
 
     private val pointerInputNode = delegate(SuspendingPointerInputModifierNode {
-        coroutineScope.launch {
-            awaitPointerEventScope {
-                while (true) {
-                    val event = awaitPointerEvent(PointerEventPass.Initial)
-                    textFieldSelectionState.isInTouchMode = !event.isPrecisePointer
-                }
+        coroutineScope {
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                with(textFieldSelectionState) { detectTouchMode() }
+            }
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectTapAndPress(onTap = { offset ->
+                    if (!isFocused) {
+                        requestFocus()
+                    }
+
+                    if (enabled && !readOnly && isFocused) {
+                        requireKeyboardController().show()
+                        textFieldSelectionState.onTapTextField(offset)
+                    }
+                })
             }
         }
-        detectTapAndPress(onTap = { offset ->
-            if (!isFocused) {
-                requestFocus()
-            }
-
-            if (enabled && !readOnly && isFocused) {
-                textInputSession?.showSoftwareKeyboard()
-
-                if (textFieldState.text.isNotEmpty()) {
-                    textFieldSelectionState.showHandles = true
-                }
-
-                // find the cursor position
-                val cursorIndex = textLayoutState.getOffsetForPosition(offset)
-                // update the state
-                if (cursorIndex >= 0) {
-                    textFieldState.edit { selectCharsIn(TextRange(cursorIndex)) }
-                }
-            }
-        })
     })
 
     var keyboardOptions: KeyboardOptions = keyboardOptions.withDefaultsFrom(filter?.keyboardOptions)
@@ -204,7 +194,7 @@
                     focusManager.moveFocus(FocusDirection.Previous)
                 }
                 ImeAction.Done -> {
-                    textInputSession?.hideSoftwareKeyboard()
+                    requireKeyboardController().hide()
                 }
                 ImeAction.Go, ImeAction.Search, ImeAction.Send,
                 ImeAction.Default, ImeAction.None -> Unit
@@ -228,8 +218,8 @@
     }
 
     /**
-     * A coroutine job that observes only text changes to hide selection and cursor handles when
-     * content changes.
+     * A coroutine job that observes text and layout changes in selection state to react to those
+     * changes.
      */
     private var inputSessionJob: Job? = null
 
@@ -254,6 +244,7 @@
         val previousTextFieldState = this.textFieldState
         val previousKeyboardOptions = this.keyboardOptions
         val previousTextFieldSelectionState = this.textFieldSelectionState
+        val previousFilter = this.filter
 
         // Apply the diff.
         this.textFieldState = textFieldState
@@ -268,11 +259,11 @@
         this.singleLine = singleLine
 
         // React to diff.
-        // If made writable while focused, or we got a completely new state instance,
-        // start a new input session.
+        // Something about the session changed, restart the session.
         if (writeable != previousWriteable ||
             textFieldState != previousTextFieldState ||
-            keyboardOptions != previousKeyboardOptions
+            keyboardOptions != previousKeyboardOptions ||
+            filter != previousFilter
         ) {
             if (writeable && isFocused) {
                 // The old session will be implicitly disposed.
@@ -283,7 +274,6 @@
             }
         }
 
-        textInputSession?.setFilter(filter)
         textFieldKeyEventHandler.setFilter(filter)
 
         if (textFieldSelectionState != previousTextFieldSelectionState) {
@@ -432,7 +422,7 @@
         inputSessionJob = coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
             previousInputSessionJob?.cancelAndJoin()
             launch(start = CoroutineStart.UNDISPATCHED) {
-                textFieldSelectionState.observeTextChanges()
+                textFieldSelectionState.observeChanges()
             }
         }
     }
@@ -443,6 +433,10 @@
         textInputSession?.dispose()
         textInputSession = null
     }
+
+    private fun requireKeyboardController(): SoftwareKeyboardController =
+        currentValueOf(LocalSoftwareKeyboardController)
+            ?: error("No software keyboard controller")
 }
 
 /**
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextInputSession.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextInputSession.kt
index a916c38..cd2d656 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextInputSession.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextInputSession.kt
@@ -21,7 +21,6 @@
 import android.view.KeyEvent
 import android.view.inputmethod.InputConnection
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.TextEditFilter
 import androidx.compose.foundation.text2.input.TextFieldCharSequence
 import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.ui.text.input.ImeAction
@@ -45,21 +44,6 @@
     val isOpen: Boolean
 
     /**
-     * Sets an optional [TextEditFilter] to be used when processing input.
-     */
-    fun setFilter(filter: TextEditFilter?)
-
-    /**
-     * Request this session to show the software keyboard.
-     */
-    fun showSoftwareKeyboard()
-
-    /**
-     * Request this session to hide the software keyboard.
-     */
-    fun hideSoftwareKeyboard()
-
-    /**
      * Destroy this session and clear resources.
      */
     fun dispose()
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/selection/TextFieldSelectionState.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/selection/TextFieldSelectionState.kt
index 87a1409..9527411 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/selection/TextFieldSelectionState.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/selection/TextFieldSelectionState.kt
@@ -18,13 +18,21 @@
 
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.gestures.detectTapAndPress
 import androidx.compose.foundation.text.DefaultCursorThickness
 import androidx.compose.foundation.text.selection.containsInclusive
 import androidx.compose.foundation.text.selection.getAdjustedCoordinates
+import androidx.compose.foundation.text.selection.getSelectionHandleCoordinates
+import androidx.compose.foundation.text.selection.isPrecisePointer
 import androidx.compose.foundation.text.selection.visibleBounds
+import androidx.compose.foundation.text2.input.TextEditFilter
+import androidx.compose.foundation.text2.input.TextEditResult
+import androidx.compose.foundation.text2.input.TextFieldBuffer
+import androidx.compose.foundation.text2.input.TextFieldBufferWithSelection
+import androidx.compose.foundation.text2.input.TextFieldCharSequence
 import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.TextOnlyMutationPolicy
 import androidx.compose.foundation.text2.input.internal.TextLayoutState
+import androidx.compose.foundation.text2.input.selectAll
 import androidx.compose.foundation.text2.input.selectCharsIn
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
@@ -34,20 +42,32 @@
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.geometry.isSpecified
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.hapticfeedback.HapticFeedbackType
+import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.platform.TextToolbarStatus
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalFoundationApi::class)
 internal class TextFieldSelectionState(
     private val textFieldState: TextFieldState,
     private val textLayoutState: TextLayoutState,
-    private val density: Density
+    var textEditFilter: TextEditFilter?,
+    var density: Density,
+    var editable: Boolean
 ) {
     /**
      * [HapticFeedback] handle to perform haptic feedback.
@@ -55,6 +75,16 @@
     var hapticFeedBack: HapticFeedback? = null
 
     /**
+     * [TextToolbar] to show floating toolbar(post-M) or primary toolbar(pre-M).
+     */
+    var textToolbar: TextToolbar? = null
+
+    /**
+     * [ClipboardManager] to perform clipboard features.
+     */
+    var clipboardManager: ClipboardManager? = null
+
+    /**
      * Whether user is interacting with the UI in touch mode.
      */
     var isInTouchMode: Boolean by mutableStateOf(true)
@@ -73,26 +103,15 @@
     var showHandles by mutableStateOf(false)
 
     /**
-     * The location where cursor handle dragging has started. [Offset.Unspecified] means there is no
-     * active dragging.
+     * Whether cursor handle is currently being dragged.
      */
-    var cursorDragStart by mutableStateOf(Offset.Unspecified)
+    private var isCursorDragging by mutableStateOf(false)
 
     /**
-     * Displacement of cursor handle compared to [cursorDragStart] while dragging.
-     * [Offset.Unspecified] means there is no active dragging.
+     * Request to show the text toolbar right now. This is not the final decider for showing the
+     * toolbar. Please refer to [observeTextToolbarVisibility] docs.
      */
-    var cursorDragDelta by mutableStateOf(Offset.Unspecified)
-
-    suspend fun observeTextChanges() {
-        val derivedTextState = derivedStateOf(TextOnlyMutationPolicy) { textFieldState.text }
-        snapshotFlow { derivedTextState.value }
-            // first value needs to be dropped because it cannot be compared to a prior value
-            .drop(1)
-            .collect {
-                showHandles = false
-            }
-    }
+    private var textToolbarVisible by mutableStateOf(false)
 
     /**
      * True if the position of the cursor is within a visible part of the window (i.e. not scrolled
@@ -103,7 +122,7 @@
         if (!existsCondition) return@derivedStateOf false
 
         // either cursor is dragging or inside visible bounds.
-        return@derivedStateOf cursorDragStart.isSpecified ||
+        return@derivedStateOf isCursorDragging ||
             textLayoutState.innerTextFieldCoordinates
                 ?.visibleBounds()
                 // Visibility of cursor handle should only be decided by changes to showHandles and
@@ -113,63 +132,6 @@
             ?: false
     }
 
-    suspend fun PointerInputScope.detectCursorHandleDragGestures() {
-        // keep track of how visible bounds change while moving the cursor handle.
-        var startContentVisibleOffset: Offset = Offset.Zero
-        detectDragGestures(
-            onDragStart = {
-                // mark start drag point
-                cursorDragStart = getAdjustedCoordinates(cursorRect.bottomCenter)
-                cursorDragDelta = Offset.Zero
-                startContentVisibleOffset = textLayoutState.innerTextFieldCoordinates
-                    ?.takeIf { textLayoutState.innerTextFieldCoordinates?.isAttached == true }
-                    ?.visibleBounds()
-                    ?.topLeft ?: Offset.Zero
-                isInTouchMode = true
-            },
-            onDragEnd = {
-                // clear any dragging state
-                cursorDragStart = Offset.Unspecified
-                cursorDragDelta = Offset.Unspecified
-                startContentVisibleOffset = Offset.Zero
-            },
-            onDragCancel = {
-                // another gesture consumed the pointer, or composable is disposed
-                cursorDragStart = Offset.Unspecified
-                cursorDragDelta = Offset.Unspecified
-                startContentVisibleOffset = Offset.Zero
-            },
-            onDrag = onDrag@{ change, dragAmount ->
-                cursorDragDelta += dragAmount
-
-                val currentContentVisibleOffset = textLayoutState.innerTextFieldCoordinates
-                    ?.visibleBounds()
-                    ?.takeIf { textLayoutState.innerTextFieldCoordinates?.isAttached == true }
-                    ?.topLeft ?: startContentVisibleOffset
-
-                // "start position + total delta" is not enough to understand the current pointer
-                // position relative to text layout. We need to also account for any changes to
-                // visible offset that's caused by auto-scrolling while dragging.
-                val currentDragPosition = cursorDragStart + cursorDragDelta +
-                    (currentContentVisibleOffset - startContentVisibleOffset)
-
-                val layoutResult = textLayoutState.layoutResult ?: return@onDrag
-                val offset = layoutResult.getOffsetForPosition(currentDragPosition)
-
-                val newSelection = TextRange(offset)
-
-                // Nothing changed, skip onValueChange hand hapticFeedback.
-                if (newSelection == textFieldState.text.selectionInChars) return@onDrag
-
-                change.consume()
-                hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
-                textFieldState.edit {
-                    selectCharsIn(newSelection)
-                }
-            }
-        )
-    }
-
     /**
      * Where the cursor should be at any given time in InnerTextField coordinates.
      */
@@ -206,4 +168,360 @@
             bottom = cursorRect.bottom
         )
     }
+
+    /**
+     * Responsible for responding to tap events on TextField.
+     */
+    fun onTapTextField(offset: Offset) {
+        if (textFieldState.text.isNotEmpty()) {
+            showHandles = true
+        }
+
+        textToolbarVisible = false
+
+        // find the cursor position
+        val cursorIndex = textLayoutState.getOffsetForPosition(offset)
+        // update the state
+        if (cursorIndex >= 0) {
+            editWithFilter {
+                selectCharsIn(TextRange(cursorIndex))
+            }
+        }
+    }
+
+    /**
+     * Implements the complete set of gestures supported by the cursor handle.
+     */
+    suspend fun PointerInputScope.cursorHandleGestures() {
+        coroutineScope {
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectTouchMode()
+            }
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectCursorHandleDragGestures()
+            }
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                detectTapAndPress { textToolbarVisible = !textToolbarVisible }
+            }
+        }
+    }
+
+    /**
+     * Detects the current pointer type in this [PointerInputScope] to update the touch mode state.
+     * This helper gesture detector should be added to all TextField pointer input receivers such
+     * as TextFieldDecorator, cursor handle, and selection handles.
+     */
+    suspend fun PointerInputScope.detectTouchMode() {
+        awaitPointerEventScope {
+            while (true) {
+                val event = awaitPointerEvent(PointerEventPass.Initial)
+                isInTouchMode = !event.isPrecisePointer
+            }
+        }
+    }
+
+    /**
+     * Starts observing changes in the current state for reactive rules. For example, the cursor
+     * handle or the selection handles should hide whenever the text content changes.
+     */
+    suspend fun observeChanges() {
+        try {
+            coroutineScope {
+                launch { observeTextChanges() }
+                launch { observeTextToolbarVisibility() }
+            }
+        } finally {
+            showHandles = false
+            if (textToolbarVisible) {
+                hideTextToolbar()
+            }
+        }
+    }
+
+    fun dispose() {
+        if (textToolbarVisible) {
+            hideTextToolbar()
+        }
+
+        textToolbar = null
+        clipboardManager = null
+        hapticFeedBack = null
+    }
+
+    private suspend fun PointerInputScope.detectCursorHandleDragGestures() {
+        // keep track of how visible bounds change while moving the cursor handle.
+        var startContentVisibleOffset: Offset = Offset.Zero
+
+        var cursorDragStart = Offset.Unspecified
+        var cursorDragDelta = Offset.Unspecified
+
+        detectDragGestures(
+            onDragStart = {
+                // mark start drag point
+                cursorDragStart = getAdjustedCoordinates(cursorRect.bottomCenter)
+                cursorDragDelta = Offset.Zero
+                startContentVisibleOffset = textLayoutState.innerTextFieldCoordinates
+                    ?.takeIf { textLayoutState.innerTextFieldCoordinates?.isAttached == true }
+                    ?.visibleBounds()
+                    ?.topLeft ?: Offset.Zero
+                isInTouchMode = true
+                isCursorDragging = true
+            },
+            onDragEnd = {
+                // clear any dragging state
+                cursorDragStart = Offset.Unspecified
+                cursorDragDelta = Offset.Unspecified
+                startContentVisibleOffset = Offset.Zero
+                isCursorDragging = false
+            },
+            onDragCancel = {
+                // another gesture consumed the pointer, or composable is disposed
+                cursorDragStart = Offset.Unspecified
+                cursorDragDelta = Offset.Unspecified
+                startContentVisibleOffset = Offset.Zero
+                isCursorDragging = false
+            },
+            onDrag = onDrag@{ change, dragAmount ->
+                cursorDragDelta += dragAmount
+
+                val currentContentVisibleOffset = textLayoutState.innerTextFieldCoordinates
+                    ?.visibleBounds()
+                    ?.takeIf { textLayoutState.innerTextFieldCoordinates?.isAttached == true }
+                    ?.topLeft ?: startContentVisibleOffset
+
+                // "start position + total delta" is not enough to understand the current pointer
+                // position relative to text layout. We need to also account for any changes to
+                // visible offset that's caused by auto-scrolling while dragging.
+                val currentDragPosition = cursorDragStart + cursorDragDelta +
+                    (currentContentVisibleOffset - startContentVisibleOffset)
+
+                val layoutResult = textLayoutState.layoutResult ?: return@onDrag
+                val offset = layoutResult.getOffsetForPosition(currentDragPosition)
+
+                val newSelection = TextRange(offset)
+
+                // Nothing changed, skip onValueChange hand hapticFeedback.
+                if (newSelection == textFieldState.text.selectionInChars) return@onDrag
+
+                change.consume()
+                hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+                editWithFilter {
+                    selectCharsIn(newSelection)
+                }
+            }
+        )
+    }
+
+    private suspend fun observeTextChanges() {
+        snapshotFlow { textFieldState.text }
+            .distinctUntilChanged(TextFieldCharSequence::contentEquals)
+            // first value needs to be dropped because it cannot be compared to a prior value
+            .drop(1)
+            .collect {
+                showHandles = false
+                textToolbarVisible = false
+            }
+    }
+
+    /**
+     * Manages the visibility of text toolbar according to current state and received events from
+     * various sources.
+     *
+     * - Tapping the cursor handle toggles the visibility of the toolbar [textToolbarVisible].
+     * - Dragging the cursor handle temporarily hides the toolbar [isCursorDragging].
+     * - Tapping somewhere on the textfield, whether it causes a cursor position change or not,
+     * fully hides the toolbar [textToolbarVisible].
+     * - Scrolling the textfield temporarily hides the toolbar [getContentRect].
+     * - When cursor leaves the visible bounds, text toolbar is temporarily hidden.
+     */
+    private suspend fun observeTextToolbarVisibility() {
+        snapshotFlow {
+            val toolbarVisibility = textToolbarVisible && // toolbar is requested
+                !isCursorDragging && // not dragging the cursor handle
+                isInTouchMode
+
+            // final visibility decision is made by contentRect visibility.
+            // if contentRect is not in visible bounds, just pass Rect.Zero to the observer so that
+            // it hides the toolbar. If Rect is successfully passed to the observer, toolbar will
+            // be shown.
+            if (!toolbarVisibility) {
+                Rect.Zero
+            } else {
+                // contentRect is calculated in root coordinates. VisibleBounds are in parent
+                // coordinates. Convert visibleBounds to root before checking the overlap.
+                val visibleBounds = textLayoutState.innerTextFieldCoordinates?.visibleBounds()
+                if (visibleBounds != null) {
+                    val visibleBoundsTopLeftInRoot = textLayoutState
+                        .innerTextFieldCoordinates
+                        ?.localToRoot(visibleBounds.topLeft)
+                    val visibleBoundsInRoot = Rect(visibleBoundsTopLeftInRoot!!, visibleBounds.size)
+                    getContentRect().takeIf { visibleBoundsInRoot.overlaps(it) } ?: Rect.Zero
+                } else {
+                    Rect.Zero
+                }
+            }
+        }.collect { rect ->
+            if (rect == Rect.Zero) {
+                hideTextToolbar()
+            } else {
+                showTextToolbar(rect)
+            }
+        }
+    }
+
+    /**
+     * Calculate selected region as [Rect]. The top is the top of the first selected
+     * line, and the bottom is the bottom of the last selected line. The left is the leftmost
+     * handle's horizontal coordinates, and the right is the rightmost handle's coordinates.
+     */
+    private fun getContentRect(): Rect {
+        val text = textFieldState.text
+        // TODO(halilibo): better stale layout result check
+        // this is basically testing whether current layoutResult was created for the current
+        // text in TextFieldState. This is a temporary check that should be improved.
+        if (textLayoutState.layoutResult?.layoutInput?.text?.length != text.length) {
+            return Rect.Zero
+        }
+        // accept cursor position as content rect when selection is collapsed
+        // contentRect is defined in innerTextField coordinates, so it needs to be realigned to
+        // root container.
+        if (text.selectionInChars.collapsed) {
+            val topLeft = textLayoutState.innerTextFieldCoordinates?.localToRoot(
+                cursorRect.topLeft
+            ) ?: Offset.Zero
+            return Rect(topLeft, cursorRect.size)
+        }
+        val startOffset =
+            textLayoutState.innerTextFieldCoordinates?.localToRoot(getHandlePosition(true))
+                ?: Offset.Zero
+        val endOffset =
+            textLayoutState.innerTextFieldCoordinates?.localToRoot(getHandlePosition(false))
+                ?: Offset.Zero
+        val startTop =
+            textLayoutState.innerTextFieldCoordinates?.localToRoot(
+                Offset(
+                    0f,
+                    textLayoutState.layoutResult?.getCursorRect(
+                        text.selectionInChars.start
+                    )?.top ?: 0f
+                )
+            )?.y ?: 0f
+        val endTop =
+            textLayoutState.innerTextFieldCoordinates?.localToRoot(
+                Offset(
+                    0f,
+                    textLayoutState.layoutResult?.getCursorRect(
+                        text.selectionInChars.end
+                    )?.top ?: 0f
+                )
+            )?.y ?: 0f
+
+        return Rect(
+            left = min(startOffset.x, endOffset.x),
+            right = max(startOffset.x, endOffset.x),
+            top = min(startTop, endTop),
+            bottom = max(startOffset.y, endOffset.y) + with(density) { 25.dp.roundToPx() }
+        )
+    }
+
+    private fun getHandlePosition(isStartHandle: Boolean): Offset {
+        val offset = if (isStartHandle) {
+            textFieldState.text.selectionInChars.start
+        } else {
+            textFieldState.text.selectionInChars.end
+        }
+        return getSelectionHandleCoordinates(
+            textLayoutResult = textLayoutState.layoutResult!!,
+            offset = offset,
+            isStart = isStartHandle,
+            areHandlesCrossed = textFieldState.text.selectionInChars.reversed
+        )
+    }
+
+    /**
+     * The method for pasting text.
+     *
+     * Get the text from [ClipboardManager]. If it's null, return.
+     * The new text should be the text before the selected text, plus the text from the
+     * [ClipboardManager], and plus the text after the selected text.
+     * Then the selection should collapse, and the new cursor offset should be the end of the
+     * newly added text.
+     */
+    private fun paste() {
+        val clipboardText = clipboardManager?.getText()?.text ?: return
+
+        editWithFilter {
+            val selection = textFieldState.text.selectionInChars
+            replace(
+                selection.min,
+                selection.max,
+                clipboardText
+            )
+            selectCharsIn(TextRange(selection.min + clipboardText.length))
+        }
+
+        showHandles = false
+        // TODO(halilibo): undoManager force snapshot
+    }
+
+    /**
+     * This function get the selected region as a Rectangle region, and pass it to [TextToolbar]
+     * to make the FloatingToolbar show up in the proper place. In addition, this function passes
+     * the copy, paste and cut method as callbacks when "copy", "cut" or "paste" is clicked.
+     *
+     * @param contentRect Rectangle region where the toolbar will be anchored.
+     */
+    private fun showTextToolbar(contentRect: Rect) {
+        val selection = textFieldState.text.selectionInChars
+
+        val paste: (() -> Unit)? = if (editable && clipboardManager?.hasText() == true) {
+            {
+                paste()
+                textToolbarVisible = false
+            }
+        } else null
+
+        val selectAll: (() -> Unit)? = if (selection.length != textFieldState.text.length) {
+            {
+                editWithFilter { selectAll() }
+            }
+        } else null
+
+        textToolbar?.showMenu(
+            rect = contentRect,
+            onPasteRequested = paste,
+            onSelectAllRequested = selectAll
+        )
+    }
+
+    /**
+     * Edits the TextFieldState content with a filter applied if available.
+     */
+    private fun editWithFilter(block: TextFieldBuffer.() -> TextEditResult) {
+        val filter = textEditFilter
+        if (filter == null) {
+            textFieldState.edit(block)
+        } else {
+            val originalValue = textFieldState.text
+            // create a new buffer to pass to TextEditFilter after edit ops
+            val buffer = TextFieldBufferWithSelection(originalValue, originalValue)
+            val textEditResult = buffer.block()
+            // selection is returned as a result, also apply it to buffer
+            val newSelection = textEditResult.calculateSelection(originalValue, buffer)
+            buffer.selectCharsIn(newSelection)
+
+            // finally filter the buffer's current status
+            textEditFilter?.filter(originalValue, buffer)
+
+            // reset the TextFieldState with the buffer's final value
+            val newValue = buffer.toTextFieldCharSequence(originalValue.compositionInChars)
+            textFieldState.editProcessor.reset(newValue)
+        }
+    }
+
+    private fun hideTextToolbar() {
+        if (textToolbar?.status == TextToolbarStatus.Shown) {
+            textToolbar?.hide()
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/integration-tests/docs-snippets/OWNERS b/compose/integration-tests/docs-snippets/OWNERS
index b91ef39..203593b 100644
--- a/compose/integration-tests/docs-snippets/OWNERS
+++ b/compose/integration-tests/docs-snippets/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 378604
 asolovay@google.com
 florinam@google.com
 jolandaverhoef@google.com
diff --git a/compose/lint/OWNERS b/compose/lint/OWNERS
index a973ce0..7fb818d 100644
--- a/compose/lint/OWNERS
+++ b/compose/lint/OWNERS
@@ -1 +1,2 @@
+# Bug component: 378604
 anbailey@google.com
\ No newline at end of file
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/NavigationSuite.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/NavigationSuite.kt
index 967aacf..02b0c7d 100644
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/NavigationSuite.kt
+++ b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/NavigationSuite.kt
@@ -16,11 +16,30 @@
 
 package androidx.compose.material3.adaptive
 
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.BadgedBox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.NavigationDrawerItem
+import androidx.compose.material3.NavigationRail
+import androidx.compose.material3.NavigationRailItem
+import androidx.compose.material3.PermanentDrawerSheet
 import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
 import androidx.compose.material3.contentColorFor
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.collection.MutableVector
+import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -33,12 +52,13 @@
  *
  * @param navigationLayoutType the current [NavigationLayoutType]
  * @param modifier the [Modifier] to be applied to the navigation suite
- * @param navigationComponent the navigation component to be displayed
+ * @param navigationComponent the navigation component to be displayed, typically
+ * [NavigationSuiteComponent]
  * @param containerColor the color used for the background of the navigation suite. Use
- * [Color.Transparent] to have no color.
+ * [Color.Transparent] to have no color
  * @param contentColor the preferred color for content inside the navigation suite. Defaults to
  * either the matching content color for [containerColor], or to the current LocalContentColor if
- * [containerColor] is not a color from the theme.
+ * [containerColor] is not a color from the theme
  * @param content the content of your screen
  *
  * TODO: Remove "internal".
@@ -88,7 +108,8 @@
         val layoutWidth = constraints.maxWidth
         val contentPlaceable = measurables.first { it.layoutId == "content" }.measure(
             if (navigationLayoutType.orientation
-                == NavigationSuiteFeature.Orientation.Horizontal) {
+                == NavigationSuiteFeature.Orientation.Horizontal
+            ) {
                 constraints.copy(
                     minHeight = layoutHeight - navigationPlaceable.height,
                     maxHeight = layoutHeight - navigationPlaceable.height
@@ -143,6 +164,83 @@
 }
 
 /**
+ * The default Material navigation component according to the current [NavigationLayoutType] to be
+ * used with the Navigation Suite.
+ *
+ * For specifics about each navigation component, see [NavigationBar], [NavigationRail], and
+ * [PermanentDrawerSheet].
+ *
+ * @param navigationLayoutType the current [NavigationLayoutType] of the [NavigationSuite]
+ * @param modifier the [Modifier] to be applied to the navigation component
+ * @param content the content inside the current navigation component, typically
+ * [navigationSuiteItem]s
+ *
+ * TODO: Remove "internal".
+ */
+@OptIn(ExperimentalMaterial3AdaptiveApi::class, ExperimentalMaterial3Api::class)
+@Composable
+internal fun NavigationSuiteComponent(
+    navigationLayoutType: NavigationLayoutType,
+    modifier: Modifier = Modifier,
+    // TODO: Add container and content color params.
+    content: NavigationSuiteComponentScope.() -> Unit
+) {
+    val scope by rememberStateOfItems(content)
+
+    when (navigationLayoutType) {
+        NavigationLayoutType.NavigationBar -> {
+            NavigationBar(modifier = modifier) {
+                scope.itemList.forEach {
+                    NavigationBarItem(
+                        modifier = it.modifier,
+                        selected = it.selected,
+                        onClick = it.onClick,
+                        icon = { NavigationItemIcon(icon = it.icon, badge = it.badge) },
+                        enabled = it.enabled,
+                        label = it.label,
+                        alwaysShowLabel = it.alwaysShowLabel,
+                        interactionSource = it.interactionSource()
+                    )
+                }
+            }
+        }
+
+        NavigationLayoutType.NavigationRail -> {
+            NavigationRail(modifier = modifier) {
+                scope.itemList.forEach {
+                    NavigationRailItem(
+                        modifier = it.modifier,
+                        selected = it.selected,
+                        onClick = it.onClick,
+                        icon = { NavigationItemIcon(icon = it.icon, badge = it.badge) },
+                        enabled = it.enabled,
+                        label = it.label,
+                        alwaysShowLabel = it.alwaysShowLabel,
+                        interactionSource = it.interactionSource()
+                    )
+                }
+            }
+        }
+
+        NavigationLayoutType.NavigationDrawer -> {
+            PermanentDrawerSheet(modifier = modifier) {
+                scope.itemList.forEach {
+                    NavigationDrawerItem(
+                        modifier = it.modifier,
+                        selected = it.selected,
+                        onClick = it.onClick,
+                        icon = it.icon,
+                        badge = it.badge,
+                        label = { it.label?.invoke() ?: Text("") },
+                        interactionSource = it.interactionSource()
+                    )
+                }
+            }
+        }
+    }
+}
+
+/**
  * A feature that describes characteristics of the navigation component of the [NavigationSuite].
  *
  * TODO: Remove "internal".
@@ -158,6 +256,7 @@
          * Navigation Bar.
          */
         Horizontal,
+
         /**
          * The navigation component of the [NavigationSuite] is vertical, such as the
          * Navigation Rail or Navigation Drawer.
@@ -196,10 +295,10 @@
 
     companion object {
         /**
-         * A navigation layout type that instructs the [NavigationSuite] to expect a
-         * [androidx.compose.material3.NavigationBar] and properly place it on the screen.
+         * A navigation layout type that instructs the [NavigationSuite] to expect a [NavigationBar]
+         * and properly place it on the screen.
          *
-         * @see androidx.compose.material3.NavigationBar
+         * @see NavigationBar
          */
         @JvmField
         val NavigationBar =
@@ -208,11 +307,12 @@
                 alignment = Alignment.BottomStart,
                 orientation = NavigationSuiteFeature.Orientation.Horizontal,
             )
+
         /**
          * A navigation layout type that instructs the [NavigationSuite] to expect a
-         * [androidx.compose.material3.NavigationRail] and properly place it on the screen.
+         * [NavigationRail] and properly place it on the screen.
          *
-         * @see androidx.compose.material3.NavigationRail
+         * @see NavigationRail
          */
         @JvmField
         val NavigationRail =
@@ -221,11 +321,12 @@
                 alignment = Alignment.TopStart,
                 orientation = NavigationSuiteFeature.Orientation.Vertical,
             )
+
         /**
          * A navigation layout type that instructs the [NavigationSuite] to expect a
-         * [androidx.compose.material3.PermanentDrawerSheet] and properly place it on the screen.
+         * [PermanentDrawerSheet] and properly place it on the screen.
          *
-         * @see androidx.compose.material3.PermanentDrawerSheet
+         * @see PermanentDrawerSheet
          */
         @JvmField
         val NavigationDrawer =
@@ -235,4 +336,162 @@
                 orientation = NavigationSuiteFeature.Orientation.Vertical,
             )
     }
-}
\ No newline at end of file
+}
+
+/**
+ * The scope associated with the [NavigationSuiteComponent].
+ *
+ * TODO: Remove "internal".
+ */
+internal interface NavigationSuiteComponentScope {
+    /**
+     * This function sets the parameters of the default Material navigation item to be used with the
+     * Navigation Suite. The item is called in [NavigationSuiteComponent], according to the current
+     * [NavigationLayoutType].
+     */
+    fun item(
+        selected: Boolean,
+        onClick: () -> Unit,
+        icon: @Composable () -> Unit,
+        modifier: Modifier,
+        enabled: Boolean,
+        label: @Composable (() -> Unit)?,
+        alwaysShowLabel: Boolean,
+        badge: (@Composable () -> Unit)?,
+        // TODO: Add colors params.
+        interactionSource: MutableInteractionSource?
+    )
+}
+
+/**
+ * This function sets the parameters of the default Material navigation item to be used with the
+ * Navigation Suite. The item is called in [NavigationSuiteComponent], according to the current
+ * [NavigationLayoutType].
+ *
+ * For specifics about each item component, see [NavigationBarItem], [NavigationRailItem], and
+ * [NavigationDrawerItem].
+ *
+ * @param selected whether this item is selected
+ * @param onClick called when this item is clicked
+ * @param icon icon for this item, typically an [Icon]
+ * @param modifier the [Modifier] to be applied to this item
+ * @param enabled controls the enabled state of this item. When `false`, this component will not
+ * respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services. Note: as of now, for [NavigationDrawerItem], this is always `true`.
+ * @param label the text label for this item
+ * @param alwaysShowLabel whether to always show the label for this item. If `false`, the label will
+ * only be shown when this item is selected. Note: for [NavigationDrawerItem] this is always `true`
+ * @param badge optional badge to show on this item
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this item. You can create and pass in your own `remember`ed instance to observe
+ * [Interaction]s and customize the appearance / behavior of this item in different states
+ *
+ * TODO: Remove "internal".
+ */
+internal fun NavigationSuiteComponentScope.navigationSuiteItem(
+    selected: Boolean,
+    onClick: () -> Unit,
+    icon: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    label: @Composable (() -> Unit)? = null,
+    alwaysShowLabel: Boolean = true,
+    badge: (@Composable () -> Unit)? = null,
+    // TODO: Add colors params.
+    interactionSource: MutableInteractionSource? = null
+) {
+    item(
+        selected = selected,
+        onClick = onClick,
+        icon = icon,
+        modifier = modifier,
+        enabled = enabled,
+        label = label,
+        badge = badge,
+        alwaysShowLabel = alwaysShowLabel,
+        interactionSource = interactionSource
+    )
+}
+
+private interface NavigationSuiteItemProvider {
+    val itemsCount: Int
+    val itemList: MutableVector<NavigationSuiteItem>
+}
+
+private class NavigationSuiteItem(
+    val selected: Boolean,
+    val onClick: () -> Unit,
+    val icon: @Composable () -> Unit,
+    val modifier: Modifier,
+    val enabled: Boolean,
+    val label: @Composable (() -> Unit)?,
+    val alwaysShowLabel: Boolean,
+    val badge: (@Composable () -> Unit)?,
+    // TODO: Add colors params.
+    val interactionSource: MutableInteractionSource?
+) {
+
+    @Composable
+    fun interactionSource(): MutableInteractionSource {
+        return interactionSource ?: remember { MutableInteractionSource() }
+    }
+}
+
+private class NavigationSuiteComponentScopeImpl : NavigationSuiteComponentScope,
+    NavigationSuiteItemProvider {
+
+    override fun item(
+        selected: Boolean,
+        onClick: () -> Unit,
+        icon: @Composable () -> Unit,
+        modifier: Modifier,
+        enabled: Boolean,
+        label: @Composable (() -> Unit)?,
+        alwaysShowLabel: Boolean,
+        badge: (@Composable () -> Unit)?,
+        // TODO: Add colors params.
+        interactionSource: MutableInteractionSource?
+    ) {
+        itemList.add(NavigationSuiteItem(
+            selected = selected,
+            onClick = onClick,
+            icon = icon,
+            modifier = modifier,
+            enabled = enabled,
+            label = label,
+            alwaysShowLabel = alwaysShowLabel,
+            badge = badge,
+            interactionSource = interactionSource
+        ))
+    }
+
+    override val itemList: MutableVector<NavigationSuiteItem> = mutableVectorOf()
+
+    override val itemsCount: Int
+        get() = itemList.size
+}
+
+@Composable
+private fun rememberStateOfItems(
+    content: NavigationSuiteComponentScope.() -> Unit
+): State<NavigationSuiteItemProvider> {
+    val latestContent = rememberUpdatedState(content)
+    return remember {
+        derivedStateOf { NavigationSuiteComponentScopeImpl().apply(latestContent.value) }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun NavigationItemIcon(
+    icon: @Composable () -> Unit,
+    badge: (@Composable () -> Unit)? = null,
+) {
+    if (badge != null) {
+        BadgedBox(badge = { badge.invoke() }) {
+            icon()
+        }
+    } else {
+        icon()
+    }
+}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 1603f04..0efc708 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -579,7 +579,8 @@
   }
 
   public final class DividerKt {
-    method @androidx.compose.runtime.Composable public static void Divider(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
+    method @androidx.compose.runtime.Composable public static void Divider(optional androidx.compose.ui.Modifier modifier, optional boolean horizontal, optional float thickness, optional long color);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Divider(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
   }
 
   public final class DrawerDefaults {
@@ -693,6 +694,21 @@
     field public static final androidx.compose.material3.FilterChipDefaults INSTANCE;
   }
 
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class FloatRange {
+    method @androidx.compose.runtime.Stable public operator float component1();
+    method @androidx.compose.runtime.Stable public operator float component2();
+    method public float getEndInclusive();
+    method public float getStart();
+    property @androidx.compose.runtime.Stable public final float endInclusive;
+    property @androidx.compose.runtime.Stable public final float start;
+    field public static final androidx.compose.material3.FloatRange.Companion Companion;
+  }
+
+  public static final class FloatRange.Companion {
+    method public long getUnspecified();
+    property public final long Unspecified;
+  }
+
   public final class FloatingActionButtonDefaults {
     method public androidx.compose.material3.FloatingActionButtonElevation bottomAppBarFabElevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.FloatingActionButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation);
@@ -1074,7 +1090,7 @@
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
-    ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+    ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
     method public float getActiveRangeEnd();
     method public float getActiveRangeStart();
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
@@ -1330,7 +1346,7 @@
   public final class SliderKt {
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(androidx.compose.material3.RangeSliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track);
     method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional int steps);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional int steps);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(androidx.compose.material3.SliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
     method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 1603f04..0efc708 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -579,7 +579,8 @@
   }
 
   public final class DividerKt {
-    method @androidx.compose.runtime.Composable public static void Divider(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
+    method @androidx.compose.runtime.Composable public static void Divider(optional androidx.compose.ui.Modifier modifier, optional boolean horizontal, optional float thickness, optional long color);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Divider(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
   }
 
   public final class DrawerDefaults {
@@ -693,6 +694,21 @@
     field public static final androidx.compose.material3.FilterChipDefaults INSTANCE;
   }
 
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class FloatRange {
+    method @androidx.compose.runtime.Stable public operator float component1();
+    method @androidx.compose.runtime.Stable public operator float component2();
+    method public float getEndInclusive();
+    method public float getStart();
+    property @androidx.compose.runtime.Stable public final float endInclusive;
+    property @androidx.compose.runtime.Stable public final float start;
+    field public static final androidx.compose.material3.FloatRange.Companion Companion;
+  }
+
+  public static final class FloatRange.Companion {
+    method public long getUnspecified();
+    property public final long Unspecified;
+  }
+
   public final class FloatingActionButtonDefaults {
     method public androidx.compose.material3.FloatingActionButtonElevation bottomAppBarFabElevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.FloatingActionButtonElevation elevation(optional float defaultElevation, optional float pressedElevation, optional float focusedElevation, optional float hoveredElevation);
@@ -1074,7 +1090,7 @@
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
-    ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
+    ctor public RangeSliderState(optional float initialActiveRangeStart, optional float initialActiveRangeEnd, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit>? initialOnValueChange, optional int steps, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished);
     method public float getActiveRangeEnd();
     method public float getActiveRangeStart();
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnValueChangeFinished();
@@ -1330,7 +1346,7 @@
   public final class SliderKt {
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(androidx.compose.material3.RangeSliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track);
     method @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional int steps);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(long value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.FloatRange,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.RangeSliderState,kotlin.Unit> track, optional int steps);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(androidx.compose.material3.SliderState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional int steps, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> thumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderState,kotlin.Unit> track, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
     method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
diff --git a/compose/material3/material3/lint-baseline.xml b/compose/material3/material3/lint-baseline.xml
index 8f77094..8409a36 100644
--- a/compose/material3/material3/lint-baseline.xml
+++ b/compose/material3/material3/lint-baseline.xml
@@ -516,6 +516,15 @@
 
     <issue
         id="PrimitiveInLambda"
+        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method RangeSlider has parameter &apos;onValueChange&apos; with type Function1&lt;? super FloatRange, Unit>."
+        errorLine1="    onValueChange: (FloatRange) -> Unit,"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;postPointerSlop&apos; with type Function2&lt;? super PointerInputChange, ? super Float, ? extends Unit>."
         errorLine1="    val postPointerSlop = { pointerInput: PointerInputChange, offset: Float ->"
         errorLine2="    ^">
@@ -525,24 +534,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method sliderSemantics has parameter &apos;onValueChange&apos; with type Function1&lt;? super Float, Unit>."
-        errorLine1="    onValueChange: (Float) -> Unit,"
-        errorLine2="                   ~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type State&lt;Function2&lt;Boolean, Float, Unit>> of &apos;getOnDrag&apos;."
-        errorLine1="    val onDrag: State&lt;(Boolean, Float) -> Unit>,"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor SliderDraggableState has parameter &apos;onDelta&apos; with type Function1&lt;? super Float, Unit>."
         errorLine1="    val onDelta: (Float) -> Unit"
         errorLine2="                 ~~~~~~~~~~~~~~~">
@@ -588,6 +579,42 @@
 
     <issue
         id="PrimitiveInLambda"
+        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor RangeSliderState has parameter &apos;initialOnValueChange&apos; with type Function1&lt;? super FloatRange, Unit>."
+        errorLine1="    initialOnValueChange: ((FloatRange) -> Unit)? = null,"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInLambda"
+        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method setOnValueChange$lint_module has parameter &apos;&lt;set-?>&apos; with type Function1&lt;? super FloatRange, Unit>."
+        errorLine1="    internal var onValueChange: (FloatRange) -> Unit = {"
+        errorLine2="    ^">
+        <location
+            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInLambda"
+        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type Function1&lt;FloatRange, Unit> of &apos;getOnValueChange$lint_module&apos;."
+        errorLine1="    internal var onValueChange: (FloatRange) -> Unit = {"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInLambda"
+        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type Function2&lt;Boolean, Float, Unit> of &apos;getOnDrag$lint_module&apos;."
+        errorLine1="    internal val onDrag: (Boolean, Float) -> Unit = { isStart, offset ->"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor DismissState has parameter &apos;positionalThreshold&apos; with type Function2&lt;? super Density, ? super Float, Float>."
         errorLine1="    positionalThreshold: Density.(totalDistance: Float) -> Float ="
         errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
index 4b4c84f..77929ef 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
@@ -56,6 +56,7 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Preview
 @Sampled
 @Composable
@@ -121,7 +122,7 @@
             }
         )
     }
-    val interactionSource = MutableInteractionSource()
+    val interactionSource = remember { MutableInteractionSource() }
     val colors = SliderDefaults.colors(thumbColor = Color.Red, activeTrackColor = Color.Red)
     Column {
         Text(text = sliderState.value.toString())
@@ -145,43 +146,53 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Preview
 @Sampled
 @Composable
 fun RangeSliderSample() {
-    var sliderPosition by remember { mutableStateOf(0f..100f) }
+    val rangeSliderState = remember {
+        RangeSliderState(
+            0f,
+            100f,
+            valueRange = 0f..100f,
+            onValueChangeFinished = {
+                // launch some business logic update with the state you hold
+                // viewModel.updateSelectedSliderValue(sliderPosition)
+            }
+        )
+    }
     Column {
-        Text(text = sliderPosition.toString())
+        Text(text = (rangeSliderState.activeRangeStart..rangeSliderState.activeRangeEnd).toString())
         RangeSlider(
-            modifier = Modifier.semantics { contentDescription = "Localized Description" },
-            value = sliderPosition,
-            onValueChange = { range -> sliderPosition = range },
+            state = rangeSliderState,
+            modifier = Modifier.semantics { contentDescription = "Localized Description" }
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Sampled
+@Composable
+fun StepRangeSliderSample() {
+    val rangeSliderState = remember {
+        RangeSliderState(
+            0f,
+            100f,
             valueRange = 0f..100f,
             onValueChangeFinished = {
                 // launch some business logic update with the state you hold
                 // viewModel.updateSelectedSliderValue(sliderPosition)
             },
+            steps = 5
         )
     }
-}
-
-@Preview
-@Sampled
-@Composable
-fun StepRangeSliderSample() {
-    var sliderPosition by remember { mutableStateOf(0f..100f) }
     Column {
-        Text(text = sliderPosition.toString())
+        Text(text = (rangeSliderState.activeRangeStart..rangeSliderState.activeRangeEnd).toString())
         RangeSlider(
-            modifier = Modifier.semantics { contentDescription = "Localized Description" },
-            steps = 5,
-            value = sliderPosition,
-            onValueChange = { range -> sliderPosition = range },
-            valueRange = 0f..1f,
-            onValueChangeFinished = {
-                // launch some business logic update with the state you hold
-                // viewModel.updateSelectedSliderValue(sliderPosition)
-            },
+            state = rangeSliderState,
+            modifier = Modifier.semantics { contentDescription = "Localized Description" }
         )
     }
 }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/CalendarModelTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/CalendarModelTest.kt
index 59cf6bd..cf1d8dd 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/CalendarModelTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/CalendarModelTest.kt
@@ -36,6 +36,7 @@
 
     @Before
     fun before() {
+        // Using the JVM Locale.getDefault() for testing purposes only.
         defaultLocale = Locale.getDefault()
     }
 
@@ -150,32 +151,56 @@
         }
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun dateInputFormat() {
-        Locale.setDefault(Locale.US)
-        assertThat(model.getDateInputFormat().patternWithDelimiters).isEqualTo("MM/dd/yyyy")
-        assertThat(model.getDateInputFormat().patternWithoutDelimiters).isEqualTo("MMddyyyy")
-        assertThat(model.getDateInputFormat().delimiter).isEqualTo('/')
+        // Note: This test ignores the parameterized model, as we need to create a new model per
+        // Locale.
+        var newModel = CalendarModelImpl(Locale.US)
+        var legacyModel = LegacyCalendarModelImpl(Locale.US)
 
-        Locale.setDefault(Locale.CHINA)
-        assertThat(model.getDateInputFormat().patternWithDelimiters).isEqualTo("yyyy/MM/dd")
-        assertThat(model.getDateInputFormat().patternWithoutDelimiters).isEqualTo("yyyyMMdd")
-        assertThat(model.getDateInputFormat().delimiter).isEqualTo('/')
+        assertThat(newModel.getDateInputFormat().patternWithDelimiters).isEqualTo("MM/dd/yyyy")
+        assertThat(newModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("MMddyyyy")
+        assertThat(newModel.getDateInputFormat().delimiter).isEqualTo('/')
+        assertThat(legacyModel.getDateInputFormat().patternWithDelimiters).isEqualTo("MM/dd/yyyy")
+        assertThat(legacyModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("MMddyyyy")
+        assertThat(legacyModel.getDateInputFormat().delimiter).isEqualTo('/')
 
-        Locale.setDefault(Locale.UK)
-        assertThat(model.getDateInputFormat().patternWithDelimiters).isEqualTo("dd/MM/yyyy")
-        assertThat(model.getDateInputFormat().patternWithoutDelimiters).isEqualTo("ddMMyyyy")
-        assertThat(model.getDateInputFormat().delimiter).isEqualTo('/')
+        newModel = CalendarModelImpl(Locale.CHINA)
+        legacyModel = LegacyCalendarModelImpl(Locale.CHINA)
+        assertThat(newModel.getDateInputFormat().patternWithDelimiters).isEqualTo("yyyy/MM/dd")
+        assertThat(newModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("yyyyMMdd")
+        assertThat(newModel.getDateInputFormat().delimiter).isEqualTo('/')
+        assertThat(legacyModel.getDateInputFormat().patternWithDelimiters).isEqualTo("yyyy/MM/dd")
+        assertThat(legacyModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("yyyyMMdd")
+        assertThat(legacyModel.getDateInputFormat().delimiter).isEqualTo('/')
 
-        Locale.setDefault(Locale.KOREA)
-        assertThat(model.getDateInputFormat().patternWithDelimiters).isEqualTo("yyyy.MM.dd")
-        assertThat(model.getDateInputFormat().patternWithoutDelimiters).isEqualTo("yyyyMMdd")
-        assertThat(model.getDateInputFormat().delimiter).isEqualTo('.')
+        newModel = CalendarModelImpl(Locale.UK)
+        legacyModel = LegacyCalendarModelImpl(Locale.UK)
+        assertThat(newModel.getDateInputFormat().patternWithDelimiters).isEqualTo("dd/MM/yyyy")
+        assertThat(newModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("ddMMyyyy")
+        assertThat(newModel.getDateInputFormat().delimiter).isEqualTo('/')
+        assertThat(legacyModel.getDateInputFormat().patternWithDelimiters).isEqualTo("dd/MM/yyyy")
+        assertThat(legacyModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("ddMMyyyy")
+        assertThat(legacyModel.getDateInputFormat().delimiter).isEqualTo('/')
 
-        Locale.setDefault(Locale("es", "CL"))
-        assertThat(model.getDateInputFormat().patternWithDelimiters).isEqualTo("dd-MM-yyyy")
-        assertThat(model.getDateInputFormat().patternWithoutDelimiters).isEqualTo("ddMMyyyy")
-        assertThat(model.getDateInputFormat().delimiter).isEqualTo('-')
+        newModel = CalendarModelImpl(Locale.KOREA)
+        legacyModel = LegacyCalendarModelImpl(Locale.KOREA)
+        assertThat(newModel.getDateInputFormat().patternWithDelimiters).isEqualTo("yyyy.MM.dd")
+        assertThat(newModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("yyyyMMdd")
+        assertThat(newModel.getDateInputFormat().delimiter).isEqualTo('.')
+        assertThat(legacyModel.getDateInputFormat().patternWithDelimiters).isEqualTo("yyyy.MM.dd")
+        assertThat(legacyModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("yyyyMMdd")
+        assertThat(legacyModel.getDateInputFormat().delimiter).isEqualTo('.')
+
+        newModel = CalendarModelImpl(Locale("es", "CL"))
+        legacyModel = LegacyCalendarModelImpl(Locale("es", "CL"))
+        assertThat(newModel.getDateInputFormat().patternWithDelimiters).isEqualTo("dd-MM-yyyy")
+        assertThat(newModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("ddMMyyyy")
+        assertThat(newModel.getDateInputFormat().delimiter).isEqualTo('-')
+        assertThat(legacyModel.getDateInputFormat().patternWithDelimiters).isEqualTo("dd-MM-yyyy")
+        assertThat(legacyModel.getDateInputFormat().patternWithoutDelimiters).isEqualTo("ddMMyyyy")
+        assertThat(legacyModel.getDateInputFormat().delimiter).isEqualTo('-')
     }
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@@ -183,8 +208,9 @@
     fun equalModelsOutput() {
         // Note: This test ignores the parameters and just runs a few equality tests for the output.
         // It will execute twice, but that should to tolerable :)
-        val newModel = CalendarModelImpl()
-        val legacyModel = LegacyCalendarModelImpl()
+        // Using the JVM Locale.getDefault() for testing purposes only.
+        val newModel = CalendarModelImpl(Locale.getDefault())
+        val legacyModel = LegacyCalendarModelImpl(Locale.getDefault())
 
         val date = newModel.getCanonicalDate(January2022Millis) // 1/1/2022
         val legacyDate = legacyModel.getCanonicalDate(January2022Millis)
@@ -216,12 +242,13 @@
         @JvmStatic
         fun parameters() =
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                // Using the JVM Locale.getDefault() for testing purposes only.
                 arrayOf(
-                    CalendarModelImpl(),
-                    LegacyCalendarModelImpl()
+                    CalendarModelImpl(Locale.getDefault()),
+                    LegacyCalendarModelImpl(Locale.getDefault())
                 )
             } else {
-                arrayOf(LegacyCalendarModelImpl())
+                arrayOf(LegacyCalendarModelImpl(Locale.getDefault()))
             }
     }
 }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerTest.kt
index 0cb837b..1d921e2 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerTest.kt
@@ -312,8 +312,12 @@
         }
         with(datePickerState) {
             assertThat(selectedDateMillis).isEqualTo(1649721600000L)
+            // Using the JVM Locale.getDefault() for testing purposes only.
             assertThat(displayedMonthMillis).isEqualTo(
-                CalendarModel.Default.getMonth(year = 2022, month = 4).startUtcTimeMillis
+                createCalendarModel(Locale.getDefault()).getMonth(
+                    year = 2022,
+                    month = 4
+                ).startUtcTimeMillis
             )
         }
     }
@@ -331,7 +335,10 @@
             // timestamp
             assertThat(selectedDateMillis).isEqualTo(1649721600000L)
             assertThat(displayedMonthMillis).isEqualTo(
-                CalendarModel.Default.getMonth(year = 2022, month = 4).startUtcTimeMillis
+                createCalendarModel(Locale.getDefault()).getMonth(
+                    year = 2022,
+                    month = 4
+                ).startUtcTimeMillis
             )
         }
     }
@@ -350,7 +357,8 @@
         with(datePickerState) {
             assertThat(selectedDateMillis).isEqualTo(1649721600000L)
             // Assert that the displayed month is the current month as of today.
-            val calendarModel = CalendarModel.Default
+            // Using the JVM Locale.getDefault() for testing purposes only.
+            val calendarModel = createCalendarModel(Locale.getDefault())
             assertThat(displayedMonthMillis).isEqualTo(
                 calendarModel.getMonth(calendarModel.today.utcTimeMillis).startUtcTimeMillis
             )
@@ -371,7 +379,7 @@
         with(datePickerState) {
             assertThat(selectedDateMillis).isNull()
             // Assert that the displayed month is the current month as of today.
-            val calendarModel = CalendarModel.Default
+            val calendarModel = createCalendarModel(Locale.getDefault())
             assertThat(displayedMonthMillis).isEqualTo(
                 calendarModel.getMonth(calendarModel.today.utcTimeMillis).startUtcTimeMillis
             )
@@ -392,7 +400,10 @@
         with(datePickerState) {
             assertThat(selectedDateMillis).isEqualTo(1649721600000L)
             assertThat(displayedMonthMillis).isEqualTo(
-                CalendarModel.Default.getMonth(year = 2022, month = 4).startUtcTimeMillis
+                createCalendarModel(Locale.getDefault()).getMonth(
+                    year = 2022,
+                    month = 4
+                ).startUtcTimeMillis
             )
             // Reset the selection
             datePickerState.selectedDateMillis = null
@@ -410,7 +421,8 @@
             datePickerState = rememberDatePickerState()
         }
 
-        val calendarModel = CalendarModel.Default
+        // Using the JVM Locale.getDefault() for testing purposes only.
+        val calendarModel = createCalendarModel(Locale.getDefault())
         with(datePickerState!!) {
             val date = calendarModel.getCanonicalDate(1649721600000L) // 04/12/2022
             val displayedMonth = calendarModel.getMonth(date)
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateRangePickerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateRangePickerTest.kt
index 0e4d7a1..31a1186 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateRangePickerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateRangePickerTest.kt
@@ -29,6 +29,7 @@
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import java.util.Calendar
+import java.util.Locale
 import java.util.TimeZone
 import org.junit.Rule
 import org.junit.Test
@@ -58,7 +59,11 @@
             assertThat(selectedStartDateMillis).isEqualTo(1649721600000L)
             assertThat(selectedEndDateMillis).isEqualTo(1649721600000L + MillisecondsIn24Hours)
             assertThat(displayedMonthMillis).isEqualTo(
-                CalendarModel.Default.getMonth(year = 2022, month = 4).startUtcTimeMillis
+                // Using the JVM Locale.getDefault() for testing purposes only.
+                createCalendarModel(Locale.getDefault()).getMonth(
+                    year = 2022,
+                    month = 4
+                ).startUtcTimeMillis
             )
         }
     }
@@ -81,7 +86,10 @@
             assertThat(selectedStartDateMillis).isEqualTo(1649721600000L)
             assertThat(selectedEndDateMillis).isEqualTo(1649721600000L + MillisecondsIn24Hours)
             assertThat(displayedMonthMillis).isEqualTo(
-                CalendarModel.Default.getMonth(year = 2022, month = 4).startUtcTimeMillis
+                createCalendarModel(Locale.getDefault()).getMonth(
+                    year = 2022,
+                    month = 4
+                ).startUtcTimeMillis
             )
         }
     }
@@ -414,7 +422,8 @@
         restorationTester.setContent {
             dateRangePickerState = rememberDateRangePickerState()
         }
-        val calendarModel = CalendarModel.Default
+        // Using the JVM Locale.getDefault() for testing purposes only.
+        val calendarModel = createCalendarModel(Locale.getDefault())
         with(dateRangePickerState!!) {
             // 04/12/2022
             val startDate =
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerUiTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerTest.kt
similarity index 75%
rename from compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerUiTest.kt
rename to compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerTest.kt
index 9e19fbb..1eeeb27 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerUiTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerTest.kt
@@ -45,44 +45,65 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-class DividerUiTest {
+class DividerTest {
 
     @get:Rule
     val rule = createComposeRule()
 
-    private val defaultHeight = 1.dp
+    private val defaultThickness = 1.dp
 
     @Test
-    fun divider_DefaultSizes() {
+    fun horizontalDivider_DefaultSizes() {
         rule
             .setMaterialContentForSizeAssertions {
                 Divider()
             }
-            .assertHeightIsEqualTo(defaultHeight)
+            .assertHeightIsEqualTo(defaultThickness)
             .assertWidthIsEqualTo(rule.rootWidth())
     }
 
     @Test
-    fun divider_CustomSizes() {
-        val height = 20.dp
+    fun horizontalDivider_CustomSizes() {
+        val thickness = 20.dp
         rule
             .setMaterialContentForSizeAssertions {
-                Divider(thickness = height)
+                Divider(thickness = thickness)
             }
             .assertWidthIsEqualTo(rule.rootWidth())
-            .assertHeightIsEqualTo(height)
+            .assertHeightIsEqualTo(thickness)
+    }
+
+    @Test
+    fun verticalDivider_DefaultSizes() {
+        rule
+            .setMaterialContentForSizeAssertions {
+                Divider(horizontal = false)
+            }
+            .assertHeightIsEqualTo(rule.rootHeight())
+            .assertWidthIsEqualTo(defaultThickness)
+    }
+
+    @Test
+    fun verticalDivider_CustomSizes() {
+        val thickness = 20.dp
+        rule
+            .setMaterialContentForSizeAssertions {
+                Divider(thickness = thickness, horizontal = false)
+            }
+            .assertWidthIsEqualTo(thickness)
+            .assertHeightIsEqualTo(rule.rootHeight())
     }
 
     @Test
     fun divider_SizesWithIndent_DoesNotChanged() {
         val indent = 75.dp
-        val height = 21.dp
+        val thickness = 21.dp
 
         rule
             .setMaterialContentForSizeAssertions {
-                Divider(modifier = Modifier.padding(start = indent), thickness = height)
+                Divider(modifier = Modifier.padding(start = indent), thickness = thickness)
             }
-            .assertHeightIsEqualTo(height)
+            .assertHeightIsEqualTo(thickness)
             .assertWidthIsEqualTo(rule.rootWidth())
     }
 
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
index 07f43c4..87a93a3 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
@@ -20,10 +20,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -55,114 +52,133 @@
 
     private val wrapperTestTag = "sliderWrapper"
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_origin() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0f) }
-                Slider(position, { position = it })
+                Slider(
+                    remember { SliderState(0f) }
+                )
             }
         }
         assertSliderAgainstGolden("slider_origin")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_origin_disabled() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0f) }
-                Slider(position, { position = it }, enabled = false)
+                Slider(
+                    remember { SliderState(0f) },
+                    enabled = false
+                )
             }
         }
         assertSliderAgainstGolden("slider_origin_disabled")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_middle() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f) }
-                Slider(position, { position = it })
+                Slider(
+                    remember { SliderState(0.5f) }
+                )
             }
         }
         assertSliderAgainstGolden("slider_middle")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_middle_dark() {
         rule.setMaterialContent(darkColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f) }
-                Slider(position, { position = it })
+                Slider(
+                    remember { SliderState(0.5f) }
+                )
             }
         }
         assertSliderAgainstGolden("slider_middle_dark")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_middle_dark_disabled() {
         rule.setMaterialContent(darkColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f) }
-                Slider(position, enabled = false, onValueChange = { position = it })
+                Slider(
+                    remember { SliderState(0.5f) },
+                    enabled = false
+                )
             }
         }
         assertSliderAgainstGolden("slider_middle_dark_disabled")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_end() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(1f) }
-                Slider(position, { position = it })
+                Slider(
+                    remember { SliderState(1f) }
+                )
             }
         }
         assertSliderAgainstGolden("slider_end")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_middle_steps() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f) }
-                Slider(position, { position = it }, steps = 5)
+                Slider(
+                    remember { SliderState(0.5f, steps = 5) }
+                )
             }
         }
         assertSliderAgainstGolden("slider_middle_steps")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_middle_steps_dark() {
         rule.setMaterialContent(darkColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f) }
-                Slider(position, { position = it }, steps = 5)
+                Slider(
+                    remember { SliderState(0.5f, steps = 5) }
+                )
             }
         }
         assertSliderAgainstGolden("slider_middle_steps_dark")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_middle_steps_disabled() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f) }
-                Slider(position, { position = it }, steps = 5, enabled = false)
+                Slider(
+                    remember { SliderState(0.5f, steps = 5) },
+                    enabled = false
+                )
             }
         }
         assertSliderAgainstGolden("slider_middle_steps_disabled")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_customColors() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f) }
                 Slider(
-                    value = position,
-                    onValueChange = { position = it },
-                    steps = 5,
+                    remember { SliderState(0.5f, steps = 5) },
                     colors = SliderDefaults.colors(
                         thumbColor = Color.Red,
                         activeTrackColor = Color.Blue,
@@ -176,15 +192,13 @@
         assertSliderAgainstGolden("slider_customColors")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderTest_customColors_disabled() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f) }
                 Slider(
-                    value = position,
-                    onValueChange = { position = it },
-                    steps = 5,
+                    remember { SliderState(0.5f, steps = 5) },
                     enabled = false,
                     // this is intentionally made to appear as enabled in disabled state for a
                     // brighter test
@@ -207,11 +221,10 @@
     fun rangeSliderTest_middle_steps_disabled() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f..1f) }
                 RangeSlider(
-                    position,
-                    { position = it },
-                    steps = 5,
+                    remember {
+                        RangeSliderState(0.5f, 1f, steps = 5)
+                    },
                     enabled = false
                 )
             }
@@ -224,8 +237,15 @@
     fun rangeSliderTest_middle_steps_enabled() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f..1f) }
-                RangeSlider(position, { position = it }, steps = 5)
+                RangeSlider(
+                    remember {
+                        RangeSliderState(
+                            0.5f,
+                            1f,
+                            steps = 5
+                        )
+                    }
+                )
             }
         }
         assertSliderAgainstGolden("rangeSlider_middle_steps_enabled")
@@ -236,8 +256,15 @@
     fun rangeSliderTest_middle_steps_dark_enabled() {
         rule.setMaterialContent(darkColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f..1f) }
-                RangeSlider(position, { position = it }, steps = 5)
+                RangeSlider(
+                    remember {
+                        RangeSliderState(
+                            0.5f,
+                            1f,
+                            steps = 5
+                        )
+                    }
+                )
             }
         }
         assertSliderAgainstGolden("rangeSlider_middle_steps_dark_enabled")
@@ -248,12 +275,12 @@
     fun rangeSliderTest_middle_steps_dark_disabled() {
         rule.setMaterialContent(darkColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f..1f) }
                 RangeSlider(
-                    position,
-                    enabled = false,
-                    onValueChange = { position = it },
-                    steps = 5)
+                    remember {
+                        RangeSliderState(0.5f, 1f, steps = 5)
+                    },
+                    enabled = false
+                )
             }
         }
         assertSliderAgainstGolden("rangeSlider_middle_steps_dark_disabled")
@@ -264,8 +291,11 @@
     fun rangeSliderTest_overlapingThumbs() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0.5f..0.51f) }
-                RangeSlider(position, { position = it })
+                RangeSlider(
+                    remember {
+                        RangeSliderState(0.5f, 0.51f)
+                    }
+                )
             }
         }
         assertSliderAgainstGolden("rangeSlider_overlapingThumbs")
@@ -276,8 +306,11 @@
     fun rangeSliderTest_fullRange() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(0f..1f) }
-                RangeSlider(position, { position = it })
+                RangeSlider(
+                    remember {
+                        RangeSliderState(0f, 1f)
+                    }
+                )
             }
         }
         assertSliderAgainstGolden("rangeSlider_fullRange")
@@ -288,11 +321,16 @@
     fun rangeSliderTest_steps_customColors() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
-                var position by remember { mutableStateOf(30f..70f) }
+                val state = remember {
+                    RangeSliderState(
+                        30f,
+                        70f,
+                        steps = 9,
+                        valueRange = 0f..100f
+                    )
+                }
                 RangeSlider(
-                    value = position,
-                    valueRange = 0f..100f,
-                    onValueChange = { position = it }, steps = 9,
+                    state = state,
                     colors = SliderDefaults.colors(
                         thumbColor = Color.Blue,
                         activeTrackColor = Color.Red,
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt
index 00f95e0..d8f1324 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt
@@ -37,6 +37,7 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.testutils.expectAssertionError
@@ -83,15 +84,14 @@
     @get:Rule
     val rule = createComposeRule()
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun sliderPosition_valueCoercion() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
         rule.setContent {
             Slider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { state.value = it },
-                valueRange = 0f..1f
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
         rule.runOnIdle {
@@ -104,21 +104,23 @@
         rule.onNodeWithTag(tag).assertRangeInfoEquals(ProgressBarRangeInfo(0f, 0f..1f, 0))
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test(expected = IllegalArgumentException::class)
     fun sliderPosition_stepsThrowWhenLessThanZero() {
         rule.setContent {
-            Slider(value = 0f, onValueChange = {}, steps = -1)
+            Slider(SliderState(initialValue = 0f, initialOnValueChange = {}, steps = -1))
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_semantics_continuous() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
 
         rule.setMaterialContent(lightColorScheme()) {
             Slider(
-                modifier = Modifier.testTag(tag), value = state.value,
-                onValueChange = { state.value = it }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -138,14 +140,15 @@
         rule.onNodeWithTag(tag).assertRangeInfoEquals(ProgressBarRangeInfo(0.7f, 0f..1f, 0))
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_semantics_stepped() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f, steps = 4)
 
         rule.setMaterialContent(lightColorScheme()) {
             Slider(
-                modifier = Modifier.testTag(tag), value = state.value,
-                onValueChange = { state.value = it }, steps = 4
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -165,22 +168,26 @@
         rule.onNodeWithTag(tag).assertRangeInfoEquals(ProgressBarRangeInfo(0.8f, 0f..1f, 4))
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_semantics_focusable() {
         rule.setMaterialContent(lightColorScheme()) {
-            Slider(value = 0f, onValueChange = {}, modifier = Modifier.testTag(tag))
+            Slider(
+                SliderState(0f),
+                modifier = Modifier.testTag(tag)
+            )
         }
 
         rule.onNodeWithTag(tag)
             .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Focused))
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_semantics_disabled() {
         rule.setMaterialContent(lightColorScheme()) {
             Slider(
-                value = 0f,
-                onValueChange = {},
+                state = SliderState(0f),
                 modifier = Modifier.testTag(tag),
                 enabled = false
             )
@@ -190,17 +197,17 @@
             .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Disabled))
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_drag() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             slop = LocalViewConfiguration.current.touchSlop
             Slider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { state.value = it }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -222,17 +229,17 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_drag_out_of_bounds() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             slop = LocalViewConfiguration.current.touchSlop
             Slider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { state.value = it }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -257,15 +264,15 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_tap() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
 
         rule.setMaterialContent(lightColorScheme()) {
             Slider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { state.value = it }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -289,9 +296,10 @@
     /**
      * Guarantee slider doesn't move as we scroll, tapping still works
      */
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_scrollableContainer() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
         val offset = mutableStateOf(0f)
 
         rule.setContent {
@@ -306,9 +314,8 @@
                         })
             ) {
                 Slider(
-                    modifier = Modifier.testTag(tag),
-                    value = state.value,
-                    onValueChange = { state.value = it }
+                    state = state,
+                    modifier = Modifier.testTag(tag)
                 )
             }
         }
@@ -343,19 +350,22 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_tap_rangeChange() {
-        val state = mutableStateOf(0f)
         val rangeEnd = mutableStateOf(0.25f)
+        lateinit var state: SliderState
 
         rule.setMaterialContent(lightColorScheme()) {
+            state = remember(rangeEnd.value) {
+                SliderState(0f, valueRange = 0f..rangeEnd.value)
+            }
             Slider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { state.value = it },
-                valueRange = 0f..rangeEnd.value
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
+
         // change to 1 since [calculateFraction] coerces between 0..1
         rule.runOnUiThread {
             rangeEnd.value = 1f
@@ -374,18 +384,18 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_drag_rtl() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                 slop = LocalViewConfiguration.current.touchSlop
                 Slider(
-                    modifier = Modifier.testTag(tag),
-                    value = state.value,
-                    onValueChange = { state.value = it }
+                    state = state,
+                    modifier = Modifier.testTag(tag)
                 )
             }
         }
@@ -409,16 +419,16 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_tap_rtl() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
 
         rule.setMaterialContent(lightColorScheme()) {
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                 Slider(
-                    modifier = Modifier.testTag(tag),
-                    value = state.value,
-                    onValueChange = { state.value = it }
+                    state = state,
+                    modifier = Modifier.testTag(tag)
                 )
             }
         }
@@ -447,18 +457,20 @@
         ((pos - start) / (end - start)).coerceIn(0f, 1f)
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_sizes() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
         rule
             .setMaterialContentForSizeAssertions(
                 parentMaxWidth = 100.dp,
                 parentMaxHeight = 100.dp
-            ) { Slider(value = state.value, onValueChange = { state.value = it }) }
+            ) { Slider(state) }
             .assertHeightIsEqualTo(48.dp)
             .assertWidthIsEqualTo(100.dp)
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_sizes_within_row() {
         val rowWidth = 100.dp
@@ -468,9 +480,8 @@
             Row(modifier = Modifier.requiredWidth(rowWidth)) {
                 Spacer(Modifier.width(spacerWidth))
                 Slider(
-                    modifier = Modifier.testTag(tag).weight(1f),
-                    value = 0f,
-                    onValueChange = {}
+                    state = SliderState(0f, {}),
+                    modifier = Modifier.testTag(tag).weight(1f)
                 )
                 Spacer(Modifier.width(spacerWidth))
             }
@@ -481,14 +492,14 @@
             .assertHeightIsEqualTo(SliderTokens.HandleHeight)
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_min_size() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(Modifier.requiredSize(0.dp)) {
                 Slider(
-                    modifier = Modifier.testTag(tag),
-                    value = 0f,
-                    onValueChange = { }
+                    state = SliderState(0f, {}),
+                    modifier = Modifier.testTag(tag)
                 )
             }
         }
@@ -498,18 +509,16 @@
             .assertHeightIsEqualTo(SliderTokens.HandleHeight)
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_noUnwantedCallbackCalls() {
-        val state = mutableStateOf(0f)
         val callCount = mutableStateOf(0f)
+        val state = SliderState(0f, { callCount.value += 1 })
 
         rule.setMaterialContent(lightColorScheme()) {
             Slider(
+                state = state,
                 modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = {
-                    callCount.value += 1
-                }
             )
         }
 
@@ -518,19 +527,16 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_valueChangeFinished_calledOnce() {
-        val state = mutableStateOf(0f)
-        val callCount = mutableStateOf(0)
+        val callCount = mutableStateOf(0f)
+        val state = SliderState(0f, onValueChangeFinished = { callCount.value += 1 })
 
         rule.setMaterialContent(lightColorScheme()) {
             Slider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChangeFinished = {
-                    callCount.value += 1
-                },
-                onValueChange = { state.value = it }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -549,19 +555,16 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_setProgress_callsOnValueChangeFinished() {
-        val state = mutableStateOf(0f)
         val callCount = mutableStateOf(0)
+        val state = SliderState(0f, onValueChangeFinished = { callCount.value += 1 })
 
         rule.setMaterialContent(lightColorScheme()) {
             Slider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChangeFinished = {
-                    callCount.value += 1
-                },
-                onValueChange = { state.value = it }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -577,6 +580,7 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_interactionSource_resetWhenDisposed() {
         val interactionSource = MutableInteractionSource()
@@ -589,9 +593,8 @@
             Box {
                 if (emitSlider) {
                     Slider(
+                        state = SliderState(0.5f, {}),
                         modifier = Modifier.testTag(tag),
-                        value = 0.5f,
-                        onValueChange = {},
                         interactionSource = interactionSource
                     )
                 }
@@ -633,15 +636,14 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_onValueChangedFinish_afterTap() {
         var changedFlag = false
         rule.setContent {
             Slider(
-                modifier = Modifier.testTag(tag),
-                value = 0.0f,
-                onValueChangeFinished = { changedFlag = true },
-                onValueChange = {}
+                state = SliderState(0f, {}, onValueChangeFinished = { changedFlag = true }),
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -655,12 +657,13 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_zero_width() {
         rule.setMaterialContentForSizeAssertions(
             parentMaxHeight = 0.dp,
             parentMaxWidth = 0.dp
-        ) { Slider(value = 1f, onValueChange = {}) }
+        ) { Slider(SliderState(1f, {})) }
             .assertHeightIsEqualTo(0.dp)
             .assertWidthIsEqualTo(0.dp)
     }
@@ -668,14 +671,13 @@
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_thumb_recomposition() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
         val recompositionCounter = SliderRecompositionCounter()
 
         rule.setContent {
             Slider(
+                state = state,
                 modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { state.value = it },
                 thumb = { sliderState -> recompositionCounter.OuterContent(sliderState) }
             )
         }
@@ -696,14 +698,13 @@
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_track_recomposition() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
         val recompositionCounter = SliderRecompositionCounter()
 
         rule.setContent {
             Slider(
+                state = state,
                 modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { state.value = it },
                 track = { sliderState -> recompositionCounter.OuterContent(sliderState) }
             )
         }
@@ -721,48 +722,49 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_parentWithInfiniteWidth_minWidth() {
-        val state = mutableStateOf(0f)
+        val state = SliderState(0f)
         rule.setMaterialContentForSizeAssertions {
             Box(modifier = Modifier.requiredWidth(Int.MAX_VALUE.dp)) {
-                Slider(value = state.value, onValueChange = { state.value = it })
+                Slider(state)
             }
         }.assertWidthIsEqualTo(48.dp)
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_rowWithInfiniteWidth() {
-        val state = mutableStateOf(0f)
         expectAssertionError(false) {
             rule.setContent {
                 Row(modifier = Modifier.requiredWidth(Int.MAX_VALUE.dp)) {
                     Slider(
-                        modifier = Modifier.weight(1f),
-                        value = state.value,
-                        onValueChange = { state.value = it }
+                        state = SliderState(0f),
+                        modifier = Modifier.weight(1f)
                     )
                 }
             }
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_dragThumb() {
-        val state = mutableStateOf(0f..1f)
+        val state = RangeSliderState(0f, 1f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             slop = LocalViewConfiguration.current.touchSlop
             RangeSlider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { range -> state.value = range }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
         rule.runOnUiThread {
-            Truth.assertThat(state.value).isEqualTo(0f..1f)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(1f)
         }
 
         var expected = 0f
@@ -775,27 +777,28 @@
                 expected = calculateFraction(left, right, centerX + 100)
             }
         rule.runOnIdle {
-            Truth.assertThat(state.value.start).isEqualTo(0f)
-            Truth.assertThat(state.value.endInclusive).isWithin(SliderTolerance).of(expected)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isWithin(SliderTolerance).of(expected)
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_drag_out_of_bounds() {
-        val state = mutableStateOf(0f..1f)
+        val state = RangeSliderState(0f, 1f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             slop = LocalViewConfiguration.current.touchSlop
             RangeSlider(
+                state = state,
                 modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { range -> state.value = range }
             )
         }
 
         rule.runOnUiThread {
-            Truth.assertThat(state.value).isEqualTo(0f..1f)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(1f)
         }
 
         var expected = 0f
@@ -812,22 +815,22 @@
                 expected = calculateFraction(left, right, centerX + 100)
             }
         rule.runOnIdle {
-            Truth.assertThat(state.value.start).isEqualTo(0f)
-            Truth.assertThat(state.value.endInclusive).isWithin(SliderTolerance).of(expected)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isWithin(SliderTolerance).of(expected)
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_drag_overlap_thumbs() {
-        val state = mutableStateOf(0.5f..1f)
+        val state = RangeSliderState(0.5f, 1f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             slop = LocalViewConfiguration.current.touchSlop
             RangeSlider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { range -> state.value = range }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -839,7 +842,8 @@
                 up()
             }
         rule.runOnIdle {
-            Truth.assertThat(state.value).isEqualTo(0.5f..0.5f)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0.5f)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(0.5f)
         }
 
         rule.onNodeWithTag(tag)
@@ -850,24 +854,26 @@
                 up()
             }
         rule.runOnIdle {
-            Truth.assertThat(state.value).isEqualTo(0.0f..0.5f)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(0.5f)
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_tap() {
-        val state = mutableStateOf(0f..1f)
+        val state = RangeSliderState(0f, 1f)
 
         rule.setMaterialContent(lightColorScheme()) {
             RangeSlider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { range -> state.value = range }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
         rule.runOnUiThread {
-            Truth.assertThat(state.value).isEqualTo(0f..1f)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(1f)
         }
 
         var expected = 0f
@@ -879,22 +885,28 @@
                 expected = calculateFraction(left, right, centerX + 50)
             }
         rule.runOnIdle {
-            Truth.assertThat(state.value.endInclusive).isWithin(SliderTolerance).of(expected)
-            Truth.assertThat(state.value.start).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isWithin(SliderTolerance).of(expected)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_tap_rangeChange() {
-        val state = mutableStateOf(0f..25f)
-        val rangeEnd = mutableStateOf(.25f)
+        val rangeEnd = mutableStateOf(0.25f)
+        lateinit var state: RangeSliderState
 
         rule.setMaterialContent(lightColorScheme()) {
+            state = remember(rangeEnd.value) {
+                RangeSliderState(
+                    0f,
+                    25f,
+                    valueRange = 0f..rangeEnd.value
+                )
+            }
             RangeSlider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { range -> state.value = range },
-                valueRange = 0f..rangeEnd.value
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
         // change to 1 since [calculateFraction] coerces between 0..1
@@ -912,28 +924,29 @@
             }
 
         rule.runOnIdle {
-            Truth.assertThat(state.value.endInclusive).isWithin(SliderTolerance).of(expected)
+            Truth.assertThat(state.activeRangeEnd).isWithin(SliderTolerance).of(expected)
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_drag_rtl() {
-        val state = mutableStateOf(0f..1f)
+        val state = RangeSliderState(0f, 1f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                 slop = LocalViewConfiguration.current.touchSlop
                 RangeSlider(
-                    modifier = Modifier.testTag(tag),
-                    value = state.value,
-                    onValueChange = { range -> state.value = range }
+                    state = state,
+                    modifier = Modifier.testTag(tag)
                 )
             }
         }
 
         rule.runOnUiThread {
-            Truth.assertThat(state.value).isEqualTo(0f..1f)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(1f)
         }
 
         var expected = 0f
@@ -948,29 +961,30 @@
                 expected = calculateFraction(left, right, centerX - 100)
             }
         rule.runOnIdle {
-            Truth.assertThat(state.value.start).isEqualTo(0f)
-            Truth.assertThat(state.value.endInclusive).isWithin(SliderTolerance).of(expected)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isWithin(SliderTolerance).of(expected)
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_drag_out_of_bounds_rtl() {
-        val state = mutableStateOf(0f..1f)
+        val state = RangeSliderState(0f, 1f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                 slop = LocalViewConfiguration.current.touchSlop
                 RangeSlider(
-                    modifier = Modifier.testTag(tag),
-                    value = state.value,
-                    onValueChange = { range -> state.value = range }
+                    state = state,
+                    modifier = Modifier.testTag(tag)
                 )
             }
         }
 
         rule.runOnUiThread {
-            Truth.assertThat(state.value).isEqualTo(0f..1f)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(1f)
         }
 
         var expected = 0f
@@ -988,27 +1002,29 @@
                 expected = calculateFraction(left, right, centerX - 100)
             }
         rule.runOnIdle {
-            Truth.assertThat(state.value.start).isEqualTo(0f)
-            Truth.assertThat(state.value.endInclusive).isWithin(SliderTolerance).of(expected)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0f)
+            Truth.assertThat(state.activeRangeEnd).isWithin(SliderTolerance).of(expected)
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_closeThumbs_dragRight() {
-        val state = mutableStateOf(0.5f..0.5f)
+        val state = RangeSliderState(0.5f, 0.5f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             slop = LocalViewConfiguration.current.touchSlop
             RangeSlider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { range -> state.value = range }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
         rule.runOnUiThread {
-            Truth.assertThat(state.value).isEqualTo(0.5f..0.5f)
+
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0.5f)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(0.5f)
         }
 
         var expected = 0f
@@ -1023,27 +1039,28 @@
                 expected = calculateFraction(left, right, centerX + 100)
             }
         rule.runOnIdle {
-            Truth.assertThat(state.value.start).isEqualTo(0.5f)
-            Truth.assertThat(state.value.endInclusive).isWithin(SliderTolerance).of(expected)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0.5f)
+            Truth.assertThat(state.activeRangeEnd).isWithin(SliderTolerance).of(expected)
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_closeThumbs_dragLeft() {
-        val state = mutableStateOf(0.5f..0.5f)
+        val state = RangeSliderState(0.5f, 0.5f)
         var slop = 0f
 
         rule.setMaterialContent(lightColorScheme()) {
             slop = LocalViewConfiguration.current.touchSlop
             RangeSlider(
-                modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { range -> state.value = range }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
         rule.runOnUiThread {
-            Truth.assertThat(state.value).isEqualTo(0.5f..0.5f)
+            Truth.assertThat(state.activeRangeStart).isEqualTo(0.5f)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(0.5f)
         }
 
         var expected = 0f
@@ -1058,24 +1075,25 @@
                 expected = calculateFraction(left, right, centerX - 100)
             }
         rule.runOnIdle {
-            Truth.assertThat(state.value.start).isWithin(SliderTolerance).of(expected)
-            Truth.assertThat(state.value.endInclusive).isEqualTo(0.5f)
+            Truth.assertThat(state.activeRangeStart).isWithin(SliderTolerance).of(expected)
+            Truth.assertThat(state.activeRangeEnd).isEqualTo(0.5f)
         }
     }
 
     /**
      * Regression test for bug: 210289161 where RangeSlider was ignoring some modifiers like weight.
      */
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_weightModifier() {
         var sliderBounds = Rect(0f, 0f, 0f, 0f)
+        val state = RangeSliderState(0f, 0.5f, {})
         rule.setMaterialContent(lightColorScheme()) {
             with(LocalDensity.current) {
                 Row(Modifier.width(500.toDp())) {
                     Spacer(Modifier.requiredSize(100.toDp()))
                     RangeSlider(
-                        value = 0f..0.5f,
-                        onValueChange = { _ -> },
+                        state = state,
                         modifier = Modifier
                             .testTag(tag)
                             .weight(1f)
@@ -1094,14 +1112,15 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_semantics_continuous() {
-        val state = mutableStateOf(0f..1f)
+        val state = RangeSliderState(0f, 1f)
 
         rule.setMaterialContent(lightColorScheme()) {
             RangeSlider(
-                modifier = Modifier.testTag(tag), value = state.value,
-                onValueChange = { range -> state.value = range }
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
@@ -1114,7 +1133,8 @@
             .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.SetProgress))
 
         rule.runOnUiThread {
-            state.value = 0.5f..0.75f
+            state.activeRangeStart = 0.5f
+            state.activeRangeEnd = 0.75f
         }
 
         rule.onAllNodes(isFocusable(), true)[0].assertRangeInfoEquals(
@@ -1146,21 +1166,26 @@
             .assertRangeInfoEquals(ProgressBarRangeInfo(0.8f, 0.6f..1f, 0))
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_semantics_stepped() {
-        val state = mutableStateOf(0f..20f)
+        val state = RangeSliderState(
+            0f,
+            20f,
+            steps = 3,
+            valueRange = 0f..20f
+        )
         // Slider with [0,5,10,15,20] possible values
         rule.setMaterialContent(lightColorScheme()) {
             RangeSlider(
-                modifier = Modifier.testTag(tag), value = state.value,
-                steps = 3,
-                valueRange = 0f..20f,
-                onValueChange = { range -> state.value = range },
+                state = state,
+                modifier = Modifier.testTag(tag)
             )
         }
 
         rule.runOnUiThread {
-            state.value = 5f..10f
+            state.activeRangeStart = 5f
+            state.activeRangeEnd = 10f
         }
 
         rule.onAllNodes(isFocusable(), true)[0].assertRangeInfoEquals(
@@ -1195,16 +1220,18 @@
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_thumb_recomposition() {
-        val state = mutableStateOf(0f..100f)
+        val state = RangeSliderState(
+            0f,
+            100f,
+            valueRange = 0f..100f
+        )
         val startRecompositionCounter = RangeSliderRecompositionCounter()
         val endRecompositionCounter = RangeSliderRecompositionCounter()
 
         rule.setContent {
             RangeSlider(
+                state = state,
                 modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { state.value = it },
-                valueRange = 0f..100f,
                 startThumb = { rangeSliderState ->
                     startRecompositionCounter.OuterContent(rangeSliderState)
                 },
@@ -1224,24 +1251,26 @@
 
         rule.runOnIdle {
             Truth.assertThat(startRecompositionCounter.outerRecomposition).isEqualTo(1)
-            Truth.assertThat(startRecompositionCounter.innerRecomposition).isEqualTo(2)
+            Truth.assertThat(startRecompositionCounter.innerRecomposition).isEqualTo(3)
             Truth.assertThat(endRecompositionCounter.outerRecomposition).isEqualTo(1)
-            Truth.assertThat(endRecompositionCounter.innerRecomposition).isEqualTo(2)
+            Truth.assertThat(endRecompositionCounter.innerRecomposition).isEqualTo(3)
         }
     }
 
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_track_recomposition() {
-        val state = mutableStateOf(0f..100f)
+        val state = RangeSliderState(
+            0f,
+            100f,
+            valueRange = 0f..100f
+        )
         val recompositionCounter = RangeSliderRecompositionCounter()
 
         rule.setContent {
             RangeSlider(
+                state = state,
                 modifier = Modifier.testTag(tag),
-                value = state.value,
-                onValueChange = { state.value = it },
-                valueRange = 0f..100f,
                 track = { rangeSliderState ->
                     recompositionCounter.OuterContent(rangeSliderState)
                 }
@@ -1262,29 +1291,27 @@
         }
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_parentWithInfiniteWidth_minWidth() {
-        val state = mutableStateOf(0f..1f)
+        val state = RangeSliderState(0f, 1f)
         rule.setMaterialContentForSizeAssertions {
             Box(modifier = Modifier.requiredWidth(Int.MAX_VALUE.dp)) {
-                RangeSlider(
-                    value = state.value,
-                    onValueChange = { range -> state.value = range }
-                )
+                RangeSlider(state)
             }
         }.assertWidthIsEqualTo(48.dp)
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun rangeSlider_rowWithInfiniteWidth() {
-        val state = mutableStateOf(0f..1f)
+        val state = RangeSliderState(0f, 1f)
         expectAssertionError(false) {
             rule.setContent {
                 Row(modifier = Modifier.requiredWidth(Int.MAX_VALUE.dp)) {
                     RangeSlider(
-                        modifier = Modifier.weight(1f),
-                        value = state.value,
-                        onValueChange = { range -> state.value = range }
+                        state = state,
+                        modifier = Modifier.weight(1f)
                     )
                 }
             }
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ActualAndroid.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ActualAndroid.android.kt
new file mode 100644
index 0000000..8c6ac4e
--- /dev/null
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ActualAndroid.android.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.core.os.ConfigurationCompat
+import java.util.Locale
+
+/**
+ * Returns the default [CalendarLocale].
+ */
+@Composable
+@ReadOnlyComposable
+internal actual fun defaultLocale(): CalendarLocale {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+        Locale24.defaultLocale()
+    } else {
+        ConfigurationCompat.getLocales(LocalConfiguration.current).get(0) ?: Locale.getDefault()
+    }
+}
+
+@RequiresApi(24)
+private class Locale24 {
+    companion object {
+        @Composable
+        @ReadOnlyComposable
+        fun defaultLocale(): CalendarLocale {
+            return LocalConfiguration.current.locales[0]
+        }
+    }
+}
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt
index da51b9f..26a7b6f 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt
@@ -23,11 +23,11 @@
  * Returns a [CalendarModel] to be used by the date picker.
  */
 @ExperimentalMaterial3Api
-internal actual fun CalendarModel(): CalendarModel {
+internal actual fun createCalendarModel(locale: CalendarLocale): CalendarModel {
     return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-        CalendarModelImpl()
+        CalendarModelImpl(locale)
     } else {
-        LegacyCalendarModelImpl()
+        LegacyCalendarModelImpl(locale)
     }
 }
 
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModelImpl.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModelImpl.android.kt
index 7f9c98f..952a15c 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModelImpl.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModelImpl.android.kt
@@ -32,14 +32,15 @@
 import java.time.format.FormatStyle
 import java.time.format.TextStyle
 import java.time.temporal.WeekFields
-import java.util.Locale
 
 /**
  * A [CalendarModel] implementation for API >= 26.
+ *
+ * @param locale a [CalendarLocale] to be used by this model
  */
 @OptIn(ExperimentalMaterial3Api::class)
 @RequiresApi(Build.VERSION_CODES.O)
-internal class CalendarModelImpl : CalendarModel {
+internal class CalendarModelImpl(locale: CalendarLocale) : CalendarModel(locale = locale) {
 
     override val today
         get(): CalendarDate {
@@ -53,11 +54,11 @@
             )
         }
 
-    override val firstDayOfWeek: Int = WeekFields.of(Locale.getDefault()).firstDayOfWeek.value
+    override val firstDayOfWeek: Int = WeekFields.of(locale).firstDayOfWeek.value
 
     override val weekdayNames: List<Pair<String, String>> =
         // This will start with Monday as the first day, according to ISO-8601.
-        with(Locale.getDefault()) {
+        with(locale) {
             DayOfWeek.values().map {
                 it.getDisplayName(
                     TextStyle.FULL,
@@ -69,7 +70,7 @@
             }
         }
 
-    override fun getDateInputFormat(locale: Locale): DateInputFormat {
+    override fun getDateInputFormat(locale: CalendarLocale): DateInputFormat {
         return datePatternAsInputFormat(
             DateTimeFormatterBuilder.getLocalizedDateTimePattern(
                 /* dateStyle = */ FormatStyle.SHORT,
@@ -129,7 +130,11 @@
         return getMonth(earlierMonth)
     }
 
-    override fun formatWithPattern(utcTimeMillis: Long, pattern: String, locale: Locale): String =
+    override fun formatWithPattern(
+        utcTimeMillis: Long,
+        pattern: String,
+        locale: CalendarLocale
+    ): String =
         CalendarModelImpl.formatWithPattern(utcTimeMillis, pattern, locale)
 
     override fun parse(date: String, pattern: String): CalendarDate? {
@@ -160,9 +165,13 @@
          *
          * @param utcTimeMillis a UTC timestamp to format (milliseconds from epoch)
          * @param pattern a date format pattern
-         * @param locale the [Locale] to use when formatting the given timestamp
+         * @param locale the [CalendarLocale] to use when formatting the given timestamp
          */
-        fun formatWithPattern(utcTimeMillis: Long, pattern: String, locale: Locale): String {
+        fun formatWithPattern(
+            utcTimeMillis: Long,
+            pattern: String,
+            locale: CalendarLocale
+        ): String {
             val formatter: DateTimeFormatter =
                 DateTimeFormatter.ofPattern(pattern, locale)
                     .withDecimalStyle(DecimalStyle.of(locale))
diff --git a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
index a0094ca..ddce3a7 100644
--- a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
@@ -40,7 +40,7 @@
     <string name="m3c_date_input_headline_description" msgid="229313757840775812">"‏התאריך שהוזן: %1$s"</string>
     <string name="m3c_date_input_no_input_description" msgid="1237013946323089826">"ללא"</string>
     <string name="m3c_date_input_invalid_not_allowed" msgid="2521768508935305279">"‏תאריך לא מורשה: %1$s"</string>
-    <string name="m3c_date_input_invalid_for_pattern" msgid="6116910750161463197">"‏התאריך לא תואם לקו ביטול הנעילה הצפוי: %1$s"</string>
+    <string name="m3c_date_input_invalid_for_pattern" msgid="6116910750161463197">"‏התאריך לא תואם למבנה הצפוי: %1$s"</string>
     <string name="m3c_date_input_invalid_year_range" msgid="7052898923934555305">"‏התאריך נמצא מחוץ לטווח השנים הצפוי %1$s - %2$s"</string>
     <string name="m3c_date_picker_switch_to_calendar_mode" msgid="1804346892470238807">"מעבר לשיטת קלט של יומן"</string>
     <string name="m3c_date_picker_switch_to_input_mode" msgid="2219746470065162704">"מעבר לשיטת קלט של טקסט"</string>
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt
index ac96c34..f99c8ab 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt
@@ -20,9 +20,11 @@
 
 /**
  * Creates a [CalendarModel] to be used by the date picker.
+ *
+ * @param locale a [CalendarLocale] that will be used by the created model
  */
 @ExperimentalMaterial3Api
-internal expect fun CalendarModel(): CalendarModel
+internal expect fun createCalendarModel(locale: CalendarLocale): CalendarModel
 
 /**
  * Formats a UTC timestamp into a string with a given date format skeleton.
@@ -44,19 +46,24 @@
     locale: CalendarLocale
 ): String
 
+/**
+ * A calendar model.
+ *
+ * @param locale a [CalendarLocale] to be used by this model
+ */
 @ExperimentalMaterial3Api
-internal interface CalendarModel {
+internal abstract class CalendarModel(val locale: CalendarLocale) {
 
     /**
      * A [CalendarDate] representing the current day.
      */
-    val today: CalendarDate
+    abstract val today: CalendarDate
 
     /**
      * Hold the first day of the week at the current `Locale` as an integer. The integer value
      * follows the ISO-8601 standard and refer to Monday as 1, and Sunday as 7.
      */
-    val firstDayOfWeek: Int
+    abstract val firstDayOfWeek: Int
 
     /**
      * Holds a list of weekday names, starting from Monday as the first day in the list.
@@ -68,7 +75,7 @@
      * day.
      * Older APIs that predate API 26 will hold a full name and the first three letters of the day.
      */
-    val weekdayNames: List<Pair<String, String>>
+    abstract val weekdayNames: List<Pair<String, String>>
 
     /**
      * Returns a [DateInputFormat] for the given [CalendarLocale].
@@ -86,7 +93,7 @@
      *  - dd.MM.yyyy
      *  - MM/dd/yyyy
      */
-    fun getDateInputFormat(locale: CalendarLocale = defaultLocale()): DateInputFormat
+    abstract fun getDateInputFormat(locale: CalendarLocale = this.locale): DateInputFormat
 
     /**
      * Returns a [CalendarDate] from a given _UTC_ time in milliseconds.
@@ -96,14 +103,14 @@
      *
      * @param timeInMillis UTC milliseconds from the epoch
      */
-    fun getCanonicalDate(timeInMillis: Long): CalendarDate
+    abstract fun getCanonicalDate(timeInMillis: Long): CalendarDate
 
     /**
      * Returns a [CalendarMonth] from a given _UTC_ time in milliseconds.
      *
      * @param timeInMillis UTC milliseconds from the epoch for the first day the month
      */
-    fun getMonth(timeInMillis: Long): CalendarMonth
+    abstract fun getMonth(timeInMillis: Long): CalendarMonth
 
     /**
      * Returns a [CalendarMonth] from a given [CalendarDate].
@@ -113,7 +120,7 @@
      *
      * @param date a [CalendarDate] to resolve into a month
      */
-    fun getMonth(date: CalendarDate): CalendarMonth
+    abstract fun getMonth(date: CalendarDate): CalendarMonth
 
     /**
      * Returns a [CalendarMonth] from a given [year] and [month].
@@ -121,14 +128,14 @@
      * @param year the month's year
      * @param month an integer representing a month (e.g. JANUARY as 1, December as 12)
      */
-    fun getMonth(year: Int, /* @IntRange(from = 1, to = 12) */ month: Int): CalendarMonth
+    abstract fun getMonth(year: Int, /* @IntRange(from = 1, to = 12) */ month: Int): CalendarMonth
 
     /**
      * Returns a day of week from a given [CalendarDate].
      *
      * @param date a [CalendarDate] to resolve
      */
-    fun getDayOfWeek(date: CalendarDate): Int
+    abstract fun getDayOfWeek(date: CalendarDate): Int
 
     /**
      * Returns a [CalendarMonth] that is computed by adding a number of months, given as
@@ -137,7 +144,7 @@
      * @param from the [CalendarMonth] to add to
      * @param addedMonthsCount the number of months to add
      */
-    fun plusMonths(from: CalendarMonth, addedMonthsCount: Int): CalendarMonth
+    abstract fun plusMonths(from: CalendarMonth, addedMonthsCount: Int): CalendarMonth
 
     /**
      * Returns a [CalendarMonth] that is computed by subtracting a number of months, given as
@@ -146,7 +153,7 @@
      * @param from the [CalendarMonth] to subtract from
      * @param subtractedMonthsCount the number of months to subtract
      */
-    fun minusMonths(from: CalendarMonth, subtractedMonthsCount: Int): CalendarMonth
+    abstract fun minusMonths(from: CalendarMonth, subtractedMonthsCount: Int): CalendarMonth
 
     /**
      * Formats a [CalendarMonth] into a string with a given date format skeleton.
@@ -158,7 +165,7 @@
     fun formatWithSkeleton(
         month: CalendarMonth,
         skeleton: String,
-        locale: CalendarLocale = defaultLocale()
+        locale: CalendarLocale = this.locale
     ): String =
         formatWithSkeleton(month.startUtcTimeMillis, skeleton, locale)
 
@@ -172,7 +179,7 @@
     fun formatWithSkeleton(
         date: CalendarDate,
         skeleton: String,
-        locale: CalendarLocale = defaultLocale()
+        locale: CalendarLocale = this.locale
     ): String = formatWithSkeleton(date.utcTimeMillis, skeleton, locale)
 
     /**
@@ -182,7 +189,11 @@
      * @param pattern a date format pattern
      * @param locale the [CalendarLocale] to use when formatting the given timestamp
      */
-    fun formatWithPattern(utcTimeMillis: Long, pattern: String, locale: CalendarLocale): String
+    abstract fun formatWithPattern(
+        utcTimeMillis: Long,
+        pattern: String,
+        locale: CalendarLocale
+    ): String
 
     /**
      * Parses a date string into a [CalendarDate].
@@ -191,15 +202,7 @@
      * @param pattern the expected date pattern to be used for parsing the date string
      * @return a [CalendarDate], or a `null` in case the parsing failed
      */
-    fun parse(date: String, pattern: String): CalendarDate?
-
-    companion object {
-
-        /**
-         * Returns a default [CalendarModel] for this environment.
-         */
-        val Default by lazy { CalendarModel() }
-    }
+    abstract fun parse(date: String, pattern: String): CalendarDate?
 }
 
 /**
@@ -226,9 +229,8 @@
     fun format(
         calendarModel: CalendarModel,
         skeleton: String,
-        locale: CalendarLocale = defaultLocale()
     ): String =
-        calendarModel.formatWithSkeleton(this, skeleton, locale)
+        calendarModel.formatWithSkeleton(this, skeleton, calendarModel.locale)
 }
 
 /**
@@ -268,10 +270,9 @@
      */
     fun format(
         calendarModel: CalendarModel,
-        skeleton: String,
-        locale: CalendarLocale = defaultLocale()
+        skeleton: String
     ): String =
-        calendarModel.formatWithSkeleton(this, skeleton, locale)
+        calendarModel.formatWithSkeleton(this, skeleton, calendarModel.locale)
 }
 
 /**
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
index e6581d6..3f7ba3a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
@@ -169,7 +169,8 @@
     showModeToggle: Boolean = true,
     colors: DatePickerColors = DatePickerDefaults.colors()
 ) {
-    val calendarModel = remember { CalendarModel() }
+    val defaultLocale = defaultLocale()
+    val calendarModel = remember(defaultLocale) { createCalendarModel(defaultLocale) }
     DateEntryContainer(
         modifier = modifier,
         title = title,
@@ -357,16 +358,20 @@
     yearRange: IntRange = DatePickerDefaults.YearRange,
     initialDisplayMode: DisplayMode = DisplayMode.Picker,
     selectableDates: SelectableDates = object : SelectableDates {}
-): DatePickerState = rememberSaveable(
-    saver = DatePickerStateImpl.Saver(selectableDates)
-) {
-    DatePickerStateImpl(
-        initialSelectedDateMillis = initialSelectedDateMillis,
-        initialDisplayedMonthMillis = initialDisplayedMonthMillis,
-        yearRange = yearRange,
-        initialDisplayMode = initialDisplayMode,
-        selectableDates = selectableDates
-    )
+): DatePickerState {
+    val locale = defaultLocale()
+    return rememberSaveable(
+        saver = DatePickerStateImpl.Saver(selectableDates, locale)
+    ) {
+        DatePickerStateImpl(
+            initialSelectedDateMillis = initialSelectedDateMillis,
+            initialDisplayedMonthMillis = initialDisplayedMonthMillis,
+            yearRange = yearRange,
+            initialDisplayMode = initialDisplayMode,
+            selectableDates = selectableDates,
+            locale = locale
+        )
+    }
 }
 
 /**
@@ -899,10 +904,11 @@
 internal abstract class BaseDatePickerStateImpl(
     @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long?,
     val yearRange: IntRange,
-    val selectableDates: SelectableDates
+    val selectableDates: SelectableDates,
+    locale: CalendarLocale
 ) {
 
-    val calendarModel = CalendarModel.Default
+    val calendarModel = createCalendarModel(locale)
 
     private var _displayedMonth =
         mutableStateOf(if (initialDisplayedMonthMillis != null) {
@@ -955,11 +961,13 @@
     @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long?,
     yearRange: IntRange,
     initialDisplayMode: DisplayMode,
-    selectableDates: SelectableDates
+    selectableDates: SelectableDates,
+    locale: CalendarLocale
 ) : BaseDatePickerStateImpl(
     initialDisplayedMonthMillis,
     yearRange,
-    selectableDates
+    selectableDates,
+    locale
 ), DatePickerState {
 
     /**
@@ -1015,7 +1023,10 @@
          * @param selectableDates a [SelectableDates] instance that is consulted to check if a date
          * is allowed
          */
-        fun Saver(selectableDates: SelectableDates): Saver<DatePickerStateImpl, Any> = listSaver(
+        fun Saver(
+            selectableDates: SelectableDates,
+            locale: CalendarLocale
+        ): Saver<DatePickerStateImpl, Any> = listSaver(
             save = {
                 listOf(
                     it.selectedDateMillis,
@@ -1031,7 +1042,8 @@
                     initialDisplayedMonthMillis = value[1] as Long?,
                     yearRange = IntRange(value[2] as Int, value[3] as Int),
                     initialDisplayMode = DisplayMode(value[4] as Int),
-                    selectableDates = selectableDates
+                    selectableDates = selectableDates,
+                    locale = locale
                 )
             }
         )
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
index 5e1e430..1f935ca 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
@@ -106,7 +106,8 @@
     showModeToggle: Boolean = true,
     colors: DatePickerColors = DatePickerDefaults.colors()
 ) {
-    val calendarModel = remember { CalendarModel() }
+    val defaultLocale = defaultLocale()
+    val calendarModel = remember(defaultLocale) { createCalendarModel(defaultLocale) }
     DateEntryContainer(
         modifier = modifier,
         title = title,
@@ -258,17 +259,21 @@
     yearRange: IntRange = DatePickerDefaults.YearRange,
     initialDisplayMode: DisplayMode = DisplayMode.Picker,
     selectableDates: SelectableDates = object : SelectableDates {}
-): DateRangePickerState = rememberSaveable(
-    saver = DateRangePickerStateImpl.Saver(selectableDates)
-) {
-    DateRangePickerStateImpl(
-        initialSelectedStartDateMillis = initialSelectedStartDateMillis,
-        initialSelectedEndDateMillis = initialSelectedEndDateMillis,
-        initialDisplayedMonthMillis = initialDisplayedMonthMillis,
-        yearRange = yearRange,
-        initialDisplayMode = initialDisplayMode,
-        selectableDates = selectableDates
-    )
+): DateRangePickerState {
+    val locale = defaultLocale()
+    return rememberSaveable(
+        saver = DateRangePickerStateImpl.Saver(selectableDates, locale)
+    ) {
+        DateRangePickerStateImpl(
+            initialSelectedStartDateMillis = initialSelectedStartDateMillis,
+            initialSelectedEndDateMillis = initialSelectedEndDateMillis,
+            initialDisplayedMonthMillis = initialDisplayedMonthMillis,
+            yearRange = yearRange,
+            initialDisplayMode = initialDisplayMode,
+            selectableDates = selectableDates,
+            locale = locale
+        )
+    }
 }
 
 /**
@@ -462,11 +467,13 @@
     @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long?,
     yearRange: IntRange,
     initialDisplayMode: DisplayMode,
-    selectableDates: SelectableDates
+    selectableDates: SelectableDates,
+    locale: CalendarLocale
 ) : BaseDatePickerStateImpl(
     initialDisplayedMonthMillis,
     yearRange,
-    selectableDates
+    selectableDates,
+    locale
 ), DateRangePickerState {
 
     /**
@@ -574,7 +581,10 @@
          * @param selectableDates a [SelectableDates] instance that is consulted to check if a date
          * is allowed
          */
-        fun Saver(selectableDates: SelectableDates): Saver<DateRangePickerStateImpl, Any> =
+        fun Saver(
+            selectableDates: SelectableDates,
+            locale: CalendarLocale
+        ): Saver<DateRangePickerStateImpl, Any> =
             listSaver(
                 save = {
                     listOf(
@@ -593,7 +603,8 @@
                         initialDisplayedMonthMillis = value[2] as Long?,
                         yearRange = IntRange(value[3] as Int, value[4] as Int),
                         initialDisplayMode = DisplayMode(value[5] as Int),
-                        selectableDates = selectableDates
+                        selectableDates = selectableDates,
+                        locale = locale
                     )
                 }
             )
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Divider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Divider.kt
index 5da509f..a6f72ec 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Divider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Divider.kt
@@ -18,8 +18,10 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
 import androidx.compose.material3.tokens.DividerTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -36,6 +38,7 @@
  * ![Divider image](https://developer.android.com/images/reference/androidx/compose/material3/divider.png)
  *
  * @param modifier the [Modifier] to be applied to this divider line.
+ * @param horizontal orientation of this divider line, `true` for horizontal, `false` for vertical
  * @param thickness thickness of this divider line. Using [Dp.Hairline] will produce a single pixel
  * divider regardless of screen density.
  * @param color color of this divider line.
@@ -43,6 +46,7 @@
 @Composable
 fun Divider(
     modifier: Modifier = Modifier,
+    horizontal: Boolean = true,
     thickness: Dp = DividerDefaults.Thickness,
     color: Color = DividerDefaults.color,
 ) {
@@ -53,12 +57,28 @@
     }
     Box(
         modifier
-            .fillMaxWidth()
-            .height(targetThickness)
+            .then(if (horizontal) {
+                Modifier.fillMaxWidth().height(targetThickness)
+            } else {
+                Modifier.fillMaxHeight().width(targetThickness)
+            })
             .background(color = color)
     )
 }
 
+@Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
+@Composable
+fun Divider(
+    modifier: Modifier = Modifier,
+    thickness: Dp = DividerDefaults.Thickness,
+    color: Color = DividerDefaults.color,
+) = Divider(
+    modifier = modifier,
+    horizontal = true,
+    thickness = thickness,
+    color = color,
+)
+
 /** Default values for [Divider] */
 object DividerDefaults {
     /** Default thickness of a divider. */
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Expect.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Expect.kt
index baf5d71..679e371 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Expect.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Expect.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.material3
 
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+
 /**
  * Represents a Locale for the calendar. This locale will be used when formatting dates, determining
  * the input format, and more.
@@ -31,6 +34,8 @@
  * Note: For JVM based platforms, this would be equivalent to [java.util.Locale.getDefault].
  */
 @OptIn(ExperimentalMaterial3Api::class)
+@Composable
+@ReadOnlyComposable
 internal expect fun defaultLocale(): CalendarLocale
 
 /**
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index a4e0331..107d849 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -89,6 +89,9 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.offset
 import androidx.compose.ui.util.lerp
+import androidx.compose.ui.util.packFloats
+import androidx.compose.ui.util.unpackFloat1
+import androidx.compose.ui.util.unpackFloat2
 import kotlin.math.abs
 import kotlin.math.floor
 import kotlin.math.max
@@ -418,8 +421,8 @@
     val endInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }
 
     RangeSlider(
-        value = value,
-        onValueChange = onValueChange,
+        value = FloatRange(value),
+        onValueChange = { onValueChange(it.start..it.endInclusive) },
         modifier = modifier,
         enabled = enabled,
         valueRange = valueRange,
@@ -508,8 +511,8 @@
 @Composable
 @ExperimentalMaterial3Api
 fun RangeSlider(
-    value: ClosedFloatingPointRange<Float>,
-    onValueChange: (ClosedFloatingPointRange<Float>) -> Unit,
+    value: FloatRange,
+    onValueChange: (FloatRange) -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
@@ -1265,8 +1268,8 @@
     lerp(a2, b2, calcFraction(a1, b1, x1))
 
 // Scale x.start, x.endInclusive from a1..b1 range to a2..b2 range
-private fun scale(a1: Float, b1: Float, x: ClosedFloatingPointRange<Float>, a2: Float, b2: Float) =
-    scale(a1, b1, x.start, a2, b2)..scale(a1, b1, x.endInclusive, a2, b2)
+private fun scale(a1: Float, b1: Float, x: FloatRange, a2: Float, b2: Float) =
+    FloatRange(scale(a1, b1, x.start, a2, b2), scale(a1, b1, x.endInclusive, a2, b2))
 
 // Calculate the 0..1 fraction that `pos` value represents between `a` and `b`
 private fun calcFraction(a: Float, b: Float, pos: Float) =
@@ -1316,7 +1319,11 @@
                 }
             }
         )
-    }.progressSemantics(state.value, state.valueRange, state.steps)
+    }.progressSemantics(
+        state.value,
+        state.valueRange.start..state.valueRange.endInclusive,
+        state.steps
+    )
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -1361,13 +1368,17 @@
                 if (resolvedValue == coerced) {
                     false
                 } else {
-                    state.onValueChange(resolvedValue..state.coercedEnd)
+                    state.onValueChange(FloatRange(resolvedValue, state.coercedEnd))
                     state.onValueChangeFinished?.invoke()
                     true
                 }
             }
         )
-    }.progressSemantics(state.coercedStart, valueRange, state.startSteps)
+    }.progressSemantics(
+        state.coercedStart,
+        valueRange,
+        state.startSteps
+    )
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -1409,13 +1420,17 @@
                 if (resolvedValue == coerced) {
                     false
                 } else {
-                    state.onValueChange(state.coercedStart..resolvedValue)
+                    state.onValueChange(FloatRange(state.coercedStart, resolvedValue))
                     state.onValueChangeFinished?.invoke()
                     true
                 }
             }
         )
-    }.progressSemantics(state.coercedEnd, valueRange, state.endSteps)
+    }.progressSemantics(
+        state.coercedEnd,
+        valueRange,
+        state.endSteps
+    )
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -1893,7 +1908,7 @@
 class RangeSliderState(
     initialActiveRangeStart: Float = 0f,
     initialActiveRangeEnd: Float = 1f,
-    initialOnValueChange: ((ClosedFloatingPointRange<Float>) -> Unit)? = null,
+    initialOnValueChange: ((FloatRange) -> Unit)? = null,
     /*@IntRange(from = 0)*/
     val steps: Int = 0,
     val valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
@@ -1931,8 +1946,8 @@
         }
         get() = activeRangeEndState
 
-    internal var onValueChange: (ClosedFloatingPointRange<Float>) -> Unit = {
-        if (it != activeRangeStart..activeRangeEnd) {
+    internal var onValueChange: (FloatRange) -> Unit = {
+        if (it != FloatRange(activeRangeStart, activeRangeEnd)) {
             initialOnValueChange?.invoke(it) ?: defaultOnValueChange(it)
         }
     }
@@ -1955,7 +1970,6 @@
     private var maxPx by mutableFloatStateOf(max(totalWidth - endThumbWidth / 2, 0f))
     private var minPx by mutableFloatStateOf(min(startThumbWidth / 2, maxPx))
 
-    @Suppress("PrimitiveInLambda")
     internal val onDrag: (Boolean, Float) -> Unit = { isStart, offset ->
         val offsetRange = if (isStart) {
             rawOffsetStart = (rawOffsetStart + offset)
@@ -1963,14 +1977,14 @@
             val offsetEnd = rawOffsetEnd
             var offsetStart = rawOffsetStart.coerceIn(minPx, offsetEnd)
             offsetStart = snapValueToTick(offsetStart, tickFractions, minPx, maxPx)
-            offsetStart..offsetEnd
+            FloatRange(offsetStart, offsetEnd)
         } else {
             rawOffsetEnd = (rawOffsetEnd + offset)
             rawOffsetStart = scaleToOffset(minPx, maxPx, activeRangeStart)
             val offsetStart = rawOffsetStart
             var offsetEnd = rawOffsetEnd.coerceIn(offsetStart, maxPx)
             offsetEnd = snapValueToTick(offsetEnd, tickFractions, minPx, maxPx)
-            offsetStart..offsetEnd
+            FloatRange(offsetStart, offsetEnd)
         }
         onValueChange(scaleToUserValue(minPx, maxPx, offsetRange))
     }
@@ -2001,7 +2015,7 @@
     internal val endSteps
         get() = floor(steps * (1f - coercedActiveRangeStartAsFraction)).toInt()
 
-    private fun defaultOnValueChange(newRange: ClosedFloatingPointRange<Float>) {
+    private fun defaultOnValueChange(newRange: FloatRange) {
         activeRangeStart = newRange.start
         activeRangeEnd = newRange.endInclusive
     }
@@ -2010,8 +2024,7 @@
     private fun scaleToUserValue(
         minPx: Float,
         maxPx: Float,
-        offset:
-        ClosedFloatingPointRange<Float>
+        offset: FloatRange
     ) = scale(minPx, maxPx, offset, valueRange.start, valueRange.endInclusive)
 
     // scales float userValue within valueRange.start..valueRange.end to within minPx..maxPx
@@ -2037,3 +2050,62 @@
         }
     }
 }
+
+@Immutable
+@JvmInline
+value class FloatRange internal constructor(
+    internal val packedValue: Long
+) {
+    @Stable
+    val start: Float
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of Size.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "FloatRange is unspecified"
+            }
+            return unpackFloat1(packedValue)
+        }
+
+    @Stable
+    val endInclusive: Float
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of Size.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "FloatRange is unspecified"
+            }
+            return unpackFloat2(packedValue)
+        }
+
+    @Stable
+    operator fun component1(): Float = start
+
+    @Stable
+    operator fun component2(): Float = endInclusive
+
+    companion object {
+        /**
+         * Represents an unspecified [FloatRange] value, usually a replacement for `null`
+         * when a primitive value is desired.
+         */
+        @Stable
+        val Unspecified = FloatRange(Float.NaN, Float.NaN)
+    }
+
+    override fun toString() = if (isSpecified) {
+        "$start..$endInclusive"
+    } else {
+        "FloatRange.Unspecified"
+    }
+}
+
+@Stable
+internal fun FloatRange(start: Float, endInclusive: Float) =
+    FloatRange(packFloats(start, endInclusive))
+
+@Stable
+internal fun FloatRange(range: ClosedFloatingPointRange<Float>) =
+    FloatRange(packFloats(range.start, range.endInclusive))
+
+@Stable
+internal val FloatRange.isSpecified: Boolean get() =
+    packedValue != FloatRange.Unspecified.packedValue
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnapFlingBehavior.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnapFlingBehavior.kt
index 3c6abaf..f28fc0a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnapFlingBehavior.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnapFlingBehavior.kt
@@ -176,12 +176,6 @@
         .let { 31 * it + lazyListState.hashCode() }
         .let { 31 * it + density.hashCode() }
 
-    private operator fun <T : Comparable<T>> ClosedFloatingPointRange<T>.component1(): T =
-        this.start
-
-    private operator fun <T : Comparable<T>> ClosedFloatingPointRange<T>.component2(): T =
-        this.endInclusive
-
     private fun findClosestOffset(
         velocity: Float,
         lazyListState: LazyListState
@@ -191,7 +185,7 @@
             return this != Float.POSITIVE_INFINITY && this != Float.NEGATIVE_INFINITY
         }
 
-        fun calculateSnappingOffsetBounds(): ClosedFloatingPointRange<Float> {
+        fun calculateSnappingOffsetBounds(): FloatRange {
             var lowerBoundOffset = Float.NEGATIVE_INFINITY
             var upperBoundOffset = Float.POSITIVE_INFINITY
 
@@ -212,7 +206,7 @@
                 }
             }
 
-            return lowerBoundOffset.rangeTo(upperBoundOffset)
+            return FloatRange(lowerBoundOffset, upperBoundOffset)
         }
 
         val (lowerBound, upperBound) = calculateSnappingOffsetBounds()
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/ActualDesktop.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/ActualDesktop.desktop.kt
new file mode 100644
index 0000000..8e21ff9
--- /dev/null
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/ActualDesktop.desktop.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+
+/**
+ * Returns the default [CalendarLocale].
+ */
+@Composable
+@ReadOnlyComposable
+internal actual fun defaultLocale(): CalendarLocale = java.util.Locale.getDefault()
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/CalendarModel.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/CalendarModel.desktop.kt
index 7d4e9f2..59e7756 100644
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/CalendarModel.desktop.kt
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/CalendarModel.desktop.kt
@@ -20,7 +20,8 @@
  * Returns a [CalendarModel] to be used by the date picker.
  */
 @ExperimentalMaterial3Api
-internal actual fun CalendarModel(): CalendarModel = LegacyCalendarModelImpl()
+internal actual fun createCalendarModel(locale: CalendarLocale): CalendarModel =
+    LegacyCalendarModelImpl(locale)
 
 /**
  * Formats a UTC timestamp into a string with a given date format skeleton.
diff --git a/compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/ActualJvm.kt b/compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/ActualJvm.jvm.kt
similarity index 91%
rename from compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/ActualJvm.kt
rename to compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/ActualJvm.jvm.kt
index d9972c6..8cc86e9 100644
--- a/compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/ActualJvm.kt
+++ b/compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/ActualJvm.jvm.kt
@@ -33,11 +33,6 @@
 actual typealias CalendarLocale = java.util.Locale
 
 /**
- * Returns the default [CalendarLocale].
- */
-internal actual fun defaultLocale(): CalendarLocale = java.util.Locale.getDefault()
-
-/**
  * Returns a string representation of an integer for the current Locale.
  */
 internal actual fun Int.toLocalString(
diff --git a/compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/LegacyCalendarModelImpl.kt b/compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/LegacyCalendarModelImpl.kt
index 42f7c34..20a2b58 100644
--- a/compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/LegacyCalendarModelImpl.kt
+++ b/compose/material3/material3/src/jvmMain/kotlin/androidx/compose/material3/LegacyCalendarModelImpl.kt
@@ -21,14 +21,15 @@
 import java.text.ParseException
 import java.text.SimpleDateFormat
 import java.util.Calendar
-import java.util.Locale
 import java.util.TimeZone
 
 /**
  * A [CalendarModel] implementation for API < 26.
+ *
+ * @param locale a [CalendarLocale] to be used by this model
  */
 @OptIn(ExperimentalMaterial3Api::class)
-internal class LegacyCalendarModelImpl : CalendarModel {
+internal class LegacyCalendarModelImpl(locale: CalendarLocale) : CalendarModel(locale = locale) {
 
     override val today
         get(): CalendarDate {
@@ -47,11 +48,12 @@
             )
         }
 
-    override val firstDayOfWeek: Int = dayInISO8601(Calendar.getInstance().firstDayOfWeek)
+    override val firstDayOfWeek: Int =
+        dayInISO8601(Calendar.getInstance(locale).firstDayOfWeek)
 
     override val weekdayNames: List<Pair<String, String>> = buildList {
-        val weekdays = DateFormatSymbols(Locale.getDefault()).weekdays
-        val shortWeekdays = DateFormatSymbols(Locale.getDefault()).shortWeekdays
+        val weekdays = DateFormatSymbols(locale).weekdays
+        val shortWeekdays = DateFormatSymbols(locale).shortWeekdays
         // Skip the first item, as it's empty, and the second item, as it represents Sunday while it
         // should be last according to ISO-8601.
         weekdays.drop(2).forEachIndexed { index, day ->
@@ -61,7 +63,7 @@
         add(Pair(weekdays[1], shortWeekdays[1]))
     }
 
-    override fun getDateInputFormat(locale: Locale): DateInputFormat {
+    override fun getDateInputFormat(locale: CalendarLocale): DateInputFormat {
         return datePatternAsInputFormat(
             (DateFormat.getDateInstance(
                 DateFormat.SHORT,
@@ -129,7 +131,11 @@
         return getMonth(earlierMonth)
     }
 
-    override fun formatWithPattern(utcTimeMillis: Long, pattern: String, locale: Locale): String =
+    override fun formatWithPattern(
+        utcTimeMillis: Long,
+        pattern: String,
+        locale: CalendarLocale
+    ): String =
         LegacyCalendarModelImpl.formatWithPattern(utcTimeMillis, pattern, locale)
 
     override fun parse(date: String, pattern: String): CalendarDate? {
@@ -162,9 +168,13 @@
          *
          * @param utcTimeMillis a UTC timestamp to format (milliseconds from epoch)
          * @param pattern a date format pattern
-         * @param locale the [Locale] to use when formatting the given timestamp
+         * @param locale the [CalendarLocale] to use when formatting the given timestamp
          */
-        fun formatWithPattern(utcTimeMillis: Long, pattern: String, locale: Locale): String {
+        fun formatWithPattern(
+            utcTimeMillis: Long,
+            pattern: String,
+            locale: CalendarLocale
+        ): String {
             val dateFormat = SimpleDateFormat(pattern, locale)
             dateFormat.timeZone = utcTimeZone
             val calendar = Calendar.getInstance(utcTimeZone)
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index a4bf32d..826aa4f 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -215,6 +215,7 @@
     method @org.jetbrains.annotations.TestOnly public static androidx.compose.runtime.ControlledComposition ControlledComposition(androidx.compose.runtime.Applier<?> applier, androidx.compose.runtime.CompositionContext parent);
     method @androidx.compose.runtime.ExperimentalComposeApi @org.jetbrains.annotations.TestOnly public static androidx.compose.runtime.ControlledComposition ControlledComposition(androidx.compose.runtime.Applier<?> applier, androidx.compose.runtime.CompositionContext parent, kotlin.coroutines.CoroutineContext recomposeCoroutineContext);
     method @androidx.compose.runtime.ExperimentalComposeApi public static kotlin.coroutines.CoroutineContext getRecomposeCoroutineContext(androidx.compose.runtime.ControlledComposition);
+    method @androidx.compose.runtime.ExperimentalComposeRuntimeApi public static androidx.compose.runtime.CompositionObserverHandle observe(androidx.compose.runtime.Composition, androidx.compose.runtime.CompositionObserver observer);
   }
 
   @androidx.compose.runtime.Stable public abstract sealed class CompositionLocal<T> {
@@ -242,6 +243,15 @@
     property public final androidx.compose.runtime.CompositionLocalMap Empty;
   }
 
+  @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface CompositionObserver {
+    method public void onBeginComposition(androidx.compose.runtime.Composition composition, java.util.Map<androidx.compose.runtime.RecomposeScope,? extends java.util.Set<?>> invalidationMap);
+    method public void onEndComposition(androidx.compose.runtime.Composition composition);
+  }
+
+  @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface CompositionObserverHandle {
+    method public void dispose();
+  }
+
   @androidx.compose.runtime.InternalComposeTracingApi public interface CompositionTracer {
     method public boolean isTraceInProgress();
     method public void traceEventEnd();
@@ -307,6 +317,9 @@
   @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This is an experimental API for Compose and is likely to change before becoming " + "stable.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER}) public @interface ExperimentalComposeApi {
   }
 
+  @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This in experimental API that may change frequently and without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER}) public @interface ExperimentalComposeRuntimeApi {
+  }
+
   @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER}) public @interface ExplicitGroupsComposable {
   }
 
@@ -456,6 +469,16 @@
     method public void invalidate();
   }
 
+  public final class RecomposeScopeImplKt {
+    method @androidx.compose.runtime.ExperimentalComposeRuntimeApi public static androidx.compose.runtime.CompositionObserverHandle observe(androidx.compose.runtime.RecomposeScope, androidx.compose.runtime.RecomposeScopeObserver observer);
+  }
+
+  @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface RecomposeScopeObserver {
+    method public void onBeginScopeComposition(androidx.compose.runtime.RecomposeScope scope);
+    method public void onEndScopeComposition(androidx.compose.runtime.RecomposeScope scope);
+    method public void onScopeDisposed(androidx.compose.runtime.RecomposeScope scope);
+  }
+
   public final class Recomposer extends androidx.compose.runtime.CompositionContext {
     ctor public Recomposer(kotlin.coroutines.CoroutineContext effectCoroutineContext);
     method public androidx.compose.runtime.RecomposerInfo asRecomposerInfo();
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 8f5a242..3ed7850 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -233,6 +233,7 @@
     method @org.jetbrains.annotations.TestOnly public static androidx.compose.runtime.ControlledComposition ControlledComposition(androidx.compose.runtime.Applier<?> applier, androidx.compose.runtime.CompositionContext parent);
     method @androidx.compose.runtime.ExperimentalComposeApi @org.jetbrains.annotations.TestOnly public static androidx.compose.runtime.ControlledComposition ControlledComposition(androidx.compose.runtime.Applier<?> applier, androidx.compose.runtime.CompositionContext parent, kotlin.coroutines.CoroutineContext recomposeCoroutineContext);
     method @androidx.compose.runtime.ExperimentalComposeApi public static kotlin.coroutines.CoroutineContext getRecomposeCoroutineContext(androidx.compose.runtime.ControlledComposition);
+    method @androidx.compose.runtime.ExperimentalComposeRuntimeApi public static androidx.compose.runtime.CompositionObserverHandle observe(androidx.compose.runtime.Composition, androidx.compose.runtime.CompositionObserver observer);
   }
 
   @androidx.compose.runtime.Stable public abstract sealed class CompositionLocal<T> {
@@ -260,6 +261,15 @@
     property public final androidx.compose.runtime.CompositionLocalMap Empty;
   }
 
+  @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface CompositionObserver {
+    method public void onBeginComposition(androidx.compose.runtime.Composition composition, java.util.Map<androidx.compose.runtime.RecomposeScope,? extends java.util.Set<?>> invalidationMap);
+    method public void onEndComposition(androidx.compose.runtime.Composition composition);
+  }
+
+  @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface CompositionObserverHandle {
+    method public void dispose();
+  }
+
   @kotlin.PublishedApi internal final class CompositionScopedCoroutineScopeCanceller implements androidx.compose.runtime.RememberObserver {
     ctor public CompositionScopedCoroutineScopeCanceller(kotlinx.coroutines.CoroutineScope coroutineScope);
     method public kotlinx.coroutines.CoroutineScope getCoroutineScope();
@@ -339,6 +349,9 @@
   @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This is an experimental API for Compose and is likely to change before becoming " + "stable.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER}) public @interface ExperimentalComposeApi {
   }
 
+  @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This in experimental API that may change frequently and without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER}) public @interface ExperimentalComposeRuntimeApi {
+  }
+
   @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER}) public @interface ExplicitGroupsComposable {
   }
 
@@ -489,9 +502,16 @@
   }
 
   public final class RecomposeScopeImplKt {
+    method @androidx.compose.runtime.ExperimentalComposeRuntimeApi public static androidx.compose.runtime.CompositionObserverHandle observe(androidx.compose.runtime.RecomposeScope, androidx.compose.runtime.RecomposeScopeObserver observer);
     method @kotlin.PublishedApi internal static int updateChangedFlags(int flags);
   }
 
+  @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface RecomposeScopeObserver {
+    method public void onBeginScopeComposition(androidx.compose.runtime.RecomposeScope scope);
+    method public void onEndScopeComposition(androidx.compose.runtime.RecomposeScope scope);
+    method public void onScopeDisposed(androidx.compose.runtime.RecomposeScope scope);
+  }
+
   public final class Recomposer extends androidx.compose.runtime.CompositionContext {
     ctor public Recomposer(kotlin.coroutines.CoroutineContext effectCoroutineContext);
     method public androidx.compose.runtime.RecomposerInfo asRecomposerInfo();
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 3b5767d..c8e0cac 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -1207,6 +1207,7 @@
 /**
  * Implementation of a composer for a mutable tree.
  */
+@OptIn(ExperimentalComposeRuntimeApi::class)
 internal class ComposerImpl(
     /**
      * An adapter that applies changes to the tree using the Applier abstraction.
@@ -1431,6 +1432,8 @@
         providersInvalidStack.push(providersInvalid.asInt())
         providersInvalid = changed(parentProvider)
         providerCache = null
+
+        // Inform observer if one is defined
         if (!forceRecomposeScopes) {
             forceRecomposeScopes = parentContext.collectingParameterInformation
         }
@@ -1850,6 +1853,11 @@
         return result as T
     }
 
+    private fun updateSlot(value: Any?) {
+        nextSlot()
+        updateValue(value)
+    }
+
     /**
      * Schedule the current value in the slot table to be updated to [value].
      *
@@ -1949,8 +1957,8 @@
     ): PersistentCompositionLocalMap {
         val providerScope = parentScope.mutate { it.putAll(currentProviders) }
         startGroup(providerMapsKey, providerMaps)
-        changed(providerScope)
-        changed(currentProviders)
+        updateSlot(providerScope)
+        updateSlot(currentProviders)
         endGroup()
         return providerScope
     }
@@ -1981,7 +1989,7 @@
             val oldValues = reader.groupGet(1) as PersistentCompositionLocalMap
 
             // skipping is true iff parentScope has not changed.
-            if (!skipping || oldValues != currentProviders) {
+            if (!skipping || reusing || oldValues != currentProviders) {
                 providers = updateProviderMapGroup(parentScope, currentProviders)
 
                 // Compare against the old scope as currentProviders might have modified the scope
@@ -1990,7 +1998,7 @@
                 // currentProviders for that key. If the scope has not changed, because these
                 // providers obscure a change in the parent as described above, re-enable skipping
                 // for the child region.
-                invalid = providers != oldScope
+                invalid = reusing || providers != oldScope
             } else {
                 // Nothing has changed
                 skipGroup()
@@ -2032,7 +2040,8 @@
             holder = CompositionContextHolder(
                 CompositionContextImpl(
                     compoundKeyHash,
-                    forceRecomposeScopes
+                    forceRecomposeScopes,
+                    (composition as? CompositionImpl)?.observerHolder
                 )
             )
             updateValue(holder)
@@ -2549,7 +2558,7 @@
         // An early out if the group and anchor are the same
         if (anchorGroup == group) return index
 
-        // Walk down from the anc ghor group counting nodes of siblings in front of this group
+        // Walk down from the anchor group counting nodes of siblings in front of this group
         var current = anchorGroup
         val nodeIndexLimit = index + (updatedNodeCount(anchorGroup) - reader.nodeCount(group))
         loop@ while (index < nodeIndexLimit) {
@@ -2841,7 +2850,7 @@
     ) {
         // Start the movable content group
         startMovableGroup(movableContentKey, content)
-        changed(parameter)
+        updateSlot(parameter)
 
         // All movable content has a compound hash value rooted at the content itself so the hash
         // value doesn't change as the content moves in the tree.
@@ -3414,9 +3423,11 @@
         }
     }
 
+    @OptIn(ExperimentalComposeRuntimeApi::class)
     private inner class CompositionContextImpl(
         override val compoundHashKey: Int,
-        override val collectingParameterInformation: Boolean
+        override val collectingParameterInformation: Boolean,
+        override val observerHolder: CompositionObserverHolder?
     ) : CompositionContext() {
         var inspectionTables: MutableSet<MutableSet<CompositionData>>? = null
         val composers = mutableSetOf<ComposerImpl>()
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
index 7621ce3..ee53f39 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
@@ -77,6 +77,27 @@
 }
 
 /**
+ * Observe the composition. Calling this twice on the same composition will implicitly dispose
+ * the previous observer. the [CompositionObserver] will be called for this composition and
+ * all sub-composition, transitively, for which this composition is a context. If, however,
+ * [observe] is called on a sub-composition, it will override the parent composition and
+ * notification for it and all sub-composition of it, will go to its observer instead of the
+ * one registered for the parent.
+ *
+ * @param observer the observer that will be informed of composition events for this
+ * composition and all sub-compositions for which this composition is the composition context.
+ * Observing a composition will prevent the parent composition's observer from receiving
+ * composition events about this composition.
+ *
+ * @return a handle that allows the observer to be disposed and detached from the composition.
+ * Disposing an observer for a composition with a parent observer will begin sending the events
+ * to the parent composition's observer.ø
+ */
+@ExperimentalComposeRuntimeApi
+fun Composition.observe(observer: CompositionObserver): CompositionObserverHandle =
+    (this as CompositionImpl).observe(observer)
+
+/**
  * A controlled composition is a [Composition] that can be directly controlled by the caller.
  *
  * This is the interface used by the [Recomposer] to control how and when a composition is
@@ -337,6 +358,7 @@
  * @param recomposeContext The coroutine context to use to recompose this composition. If left
  * `null` the controlling recomposer's default context is used.
  */
+@OptIn(ExperimentalComposeRuntimeApi::class)
 internal class CompositionImpl(
     /**
      * The parent composition from [rememberCompositionContext], for sub-compositions, or the an
@@ -459,6 +481,8 @@
 
     private var invalidationDelegateGroup: Int = 0
 
+    internal val observerHolder = CompositionObserverHolder()
+
     /**
      * The [Composer] to use to create and update the tree managed by this composition.
      */
@@ -521,6 +545,24 @@
         parent.composeInitial(this, composable)
     }
 
+    @OptIn(ExperimentalComposeRuntimeApi::class)
+    internal fun observe(observer: CompositionObserver): CompositionObserverHandle {
+        synchronized(lock) {
+            observerHolder.observer = observer
+            observerHolder.root = true
+        }
+        return object : CompositionObserverHandle {
+            override fun dispose() {
+                synchronized(lock) {
+                    if (observerHolder.observer == observer) {
+                        observerHolder.observer = null
+                        observerHolder.root = false
+                    }
+                }
+            }
+        }
+    }
+
     fun invalidateGroupsWithKey(key: Int) {
         val scopesToInvalidate = synchronized(lock) {
             slotTable.invalidateGroupsWithKey(key)
@@ -586,7 +628,14 @@
             synchronized(lock) {
                 drainPendingModificationsForCompositionLocked()
                 guardInvalidationsLocked { invalidations ->
+                    val observer = observer()
+                    @Suppress("UNCHECKED_CAST")
+                    observer?.onBeginComposition(
+                        this,
+                        invalidations.asMap() as Map<RecomposeScope, Set<Any>?>
+                    )
                     composer.composeContent(invalidations, content)
+                    observer?.onEndComposition(this)
                 }
             }
         }
@@ -789,9 +838,16 @@
         drainPendingModificationsForCompositionLocked()
         guardChanges {
             guardInvalidationsLocked { invalidations ->
+                val observer = observer()
+                @Suppress("UNCHECKED_CAST")
+                observer?.onBeginComposition(
+                    this,
+                    invalidations.asMap() as Map<RecomposeScope, Set<Any>?>
+                )
                 composer.recompose(invalidations).also { shouldDrain ->
                     // Apply would normally do this for us; do it now if apply shouldn't happen.
                     if (!shouldDrain) drainPendingModificationsLocked()
+                    observer?.onEndComposition(this)
                 }
             }
         }
@@ -1061,6 +1117,21 @@
         }
     }
 
+    private fun observer(): CompositionObserver? {
+        val holder = observerHolder
+
+        return if (holder.root) {
+            holder.observer
+        } else {
+            val parentHolder = parent.observerHolder
+            val parentObserver = parentHolder?.observer
+            if (parentObserver != holder.observer) {
+                holder.observer = parentObserver
+            }
+            parentObserver
+        }
+    }
+
     /**
      * Helper for collecting remember observers for later strictly ordered dispatch.
      */
@@ -1204,7 +1275,6 @@
         }
 
         // Called after Dex Code Swap
-        @Suppress("UNUSED_PARAMETER")
         private fun loadStateAndCompose(token: Any) {
             Recomposer.loadStateAndComposeForHotReload(token)
         }
@@ -1281,4 +1351,10 @@
             iter.remove()
         }
     }
-}
\ No newline at end of file
+}
+
+@ExperimentalComposeRuntimeApi
+internal class CompositionObserverHolder(
+    var observer: CompositionObserver? = null,
+    var root: Boolean = false,
+)
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
index 5295c43..b614f8a 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
@@ -34,10 +34,12 @@
  *
  * @see rememberCompositionContext
  */
-@OptIn(InternalComposeApi::class)
+@OptIn(InternalComposeApi::class, ExperimentalComposeRuntimeApi::class)
 abstract class CompositionContext internal constructor() {
     internal abstract val compoundHashKey: Int
     internal abstract val collectingParameterInformation: Boolean
+    internal open val observerHolder: CompositionObserverHolder? get() = null
+
     /**
      *  The [CoroutineContext] with which effects for the composition will be executed in.
      **/
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionObserver.kt
new file mode 100644
index 0000000..ad32ecd
--- /dev/null
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionObserver.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime
+
+@RequiresOptIn(
+    level = RequiresOptIn.Level.ERROR,
+    message = "This in experimental API that may change frequently and without warning."
+)
+@Target(
+    AnnotationTarget.CLASS,
+    AnnotationTarget.FUNCTION,
+    AnnotationTarget.PROPERTY,
+    AnnotationTarget.FIELD,
+    AnnotationTarget.PROPERTY_GETTER,
+)
+@Retention(AnnotationRetention.BINARY)
+annotation class ExperimentalComposeRuntimeApi
+
+/**
+ * Observe when the composition begins and ends.
+ */
+@ExperimentalComposeRuntimeApi
+@Suppress("CallbackName")
+interface CompositionObserver {
+    /**
+     * Called when the composition begins on the [composition]. The [invalidationMap] a map of
+     * invalid recompose scopes that are scheduled to be recomposed. The [CompositionObserver]
+     * will be called for the [composition].
+     *
+     * The scopes in the [invalidationMap] are not guaranteed to be composed. Some cases where they
+     * are not composed are 1) the scope is no longer part of the composition (e.g the parent scope
+     * no longer executed the code branch the scope was a part of) 2) the scope is part of movable
+     * content that was moved out of the composition.
+     *
+     * In the case of movable content, the scope will be recomposed as part of a different
+     * composition when it is moved to that composition or it might be discarded if no other
+     * composition claims it.
+     *
+     * @param composition the composition that is beginning to be recomposed
+     * @param invalidationMap the recompose scopes that will be recomposed by this composition.
+     *    This list is empty for the initial composition.
+     */
+    fun onBeginComposition(
+        composition: Composition,
+        invalidationMap: Map<RecomposeScope, Set<Any>?>
+    )
+
+    /**
+     * Called after composition has been completed for [composition].
+     */
+    fun onEndComposition(composition: Composition)
+}
+
+/**
+ * Observer when a recompose scope is being recomposed or when the scope is disposed.
+ */
+@ExperimentalComposeRuntimeApi
+@Suppress("CallbackName")
+interface RecomposeScopeObserver {
+    /**
+     * Called just before the recompose scope's recompose lambda is invoked.
+     */
+    fun onBeginScopeComposition(scope: RecomposeScope)
+
+    /**
+     * Called just after the recompose scopes' recompose lambda returns.
+     */
+    fun onEndScopeComposition(scope: RecomposeScope)
+
+    /**
+     * Called when the recompose scope is disposed.
+     */
+    fun onScopeDisposed(scope: RecomposeScope)
+}
+
+/**
+ * The handle returned by [Composition.observe] and [RecomposeScope.observe]. Calling [dispose]
+ * will prevent further composition observation events from being sent to the registered observer.
+ */
+@ExperimentalComposeRuntimeApi
+interface CompositionObserverHandle {
+    /**
+     * Unregister the observer.
+     */
+    fun dispose()
+}
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt
index 61f8d70..dcc373f 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt
@@ -35,6 +35,13 @@
     fun invalidate()
 }
 
+/**
+ * Observer when this scope recomposes.
+ */
+@ExperimentalComposeRuntimeApi
+fun RecomposeScope.observe(observer: RecomposeScopeObserver): CompositionObserverHandle =
+    (this as RecomposeScopeImpl).observe(observer)
+
 private const val changedLowBitMask = 0b001_001_001_001_001_001_001_001_001_001_0
 private const val changedHighBitMask = changedLowBitMask shl 1
 private const val changedMask = (changedLowBitMask or changedHighBitMask).inv()
@@ -65,6 +72,8 @@
     fun recordReadOf(value: Any)
 }
 
+private val callbackLock = Any()
+
 /**
  * A RecomposeScope is created for a region of the composition that can be recomposed independently
  * of the rest of the composition. The composer will position the slot table to the location
@@ -160,15 +169,50 @@
     private var block: ((Composer, Int) -> Unit)? = null
 
     /**
+     * The recompose scope observer, if one is registered.
+     */
+    @ExperimentalComposeRuntimeApi
+    private var observer: RecomposeScopeObserver? = null
+
+    /**
      * Restart the scope's composition. It is an error if [block] was not updated. The code
      * generated by the compiler ensures that when the recompose scope is used then [block] will
      * be set but it might occur if the compiler is out-of-date (or ahead of the runtime) or
      * incorrect direct calls to [Composer.startRestartGroup] and [Composer.endRestartGroup].
      */
+    @OptIn(ExperimentalComposeRuntimeApi::class)
     fun compose(composer: Composer) {
+        @Suppress("PrimitiveInLambda")
+        val block = block
+        val observer = observer
+        if (observer != null && block != null) {
+            observer.onBeginScopeComposition(this)
+            try {
+                block(composer, 1)
+            } finally {
+                observer.onEndScopeComposition(this)
+            }
+            return
+        }
         block?.invoke(composer, 1) ?: error("Invalid restart scope")
     }
 
+    @ExperimentalComposeRuntimeApi
+    internal fun observe(observer: RecomposeScopeObserver): CompositionObserverHandle {
+        synchronized(callbackLock) {
+            this.observer = observer
+        }
+        return object : CompositionObserverHandle {
+            override fun dispose() {
+                synchronized(callbackLock) {
+                    if (this@RecomposeScopeImpl.observer == observer) {
+                        this@RecomposeScopeImpl.observer = null
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Invalidate the group which will cause [owner] to request this scope be recomposed,
      * and an [InvalidationResult] will be returned.
@@ -185,6 +229,8 @@
         owner = null
         trackedInstances = null
         trackedDependencies = null
+        @OptIn(ExperimentalComposeRuntimeApi::class)
+        observer?.onScopeDisposed(this)
     }
 
     /**
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt
index bc600cc..5328b4d 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/IdentityArrayMap.kt
@@ -216,4 +216,82 @@
         // We should insert at the end
         return -(size + 1)
     }
+
+    @Suppress("UNCHECKED_CAST")
+    fun asMap(): Map<Key, Value> = object : Map<Key, Value> {
+        override val entries: Set<Map.Entry<Key, Value>>
+            get() = object : Set<Map.Entry<Key, Value>> {
+                override val size: Int get() = this@IdentityArrayMap.size
+                override fun isEmpty(): Boolean = this@IdentityArrayMap.isEmpty()
+                override fun iterator(): Iterator<Map.Entry<Key, Value>> =
+                    sequence<Map.Entry<Key, Value>> {
+                        for (index in 0 until this@IdentityArrayMap.size) {
+                            yield(
+                                object : Map.Entry<Key, Value> {
+                                    override val key: Key =
+                                        this@IdentityArrayMap.keys[index] as Key
+                                    override val value: Value =
+                                        this@IdentityArrayMap.values[index] as Value
+                                }
+                            )
+                        }
+                    }.iterator()
+                override fun containsAll(elements: Collection<Map.Entry<Key, Value>>): Boolean =
+                    elements.all { contains(it) }
+
+                override fun contains(element: Map.Entry<Key, Value>): Boolean =
+                    this@IdentityArrayMap[element.key] === element.value
+            }
+
+        override val keys: Set<Key> get() = object : Set<Key> {
+            override val size: Int get() = this@IdentityArrayMap.size
+            override fun isEmpty(): Boolean = this@IdentityArrayMap.isEmpty()
+            override fun iterator(): Iterator<Key> = sequence {
+                for (index in 0 until this@IdentityArrayMap.size) {
+                    yield(this@IdentityArrayMap.keys[index] as Key)
+                }
+            }.iterator()
+
+            override fun containsAll(elements: Collection<Key>): Boolean {
+                for (key in elements) {
+                    if (!contains(key)) return false
+                }
+                return true
+            }
+
+            override fun contains(element: Key): Boolean = this@IdentityArrayMap.contains(element)
+        }
+
+        override val size: Int get() = this@IdentityArrayMap.size
+        override val values: Collection<Value> get() = object : Collection<Value> {
+            override val size: Int get() = this@IdentityArrayMap.size
+            override fun isEmpty(): Boolean = this@IdentityArrayMap.isEmpty()
+            override fun iterator(): Iterator<Value> = sequence {
+                for (index in 0 until this@IdentityArrayMap.size) {
+                    yield(this@IdentityArrayMap.values[index] as Value)
+                }
+            }.iterator()
+
+            override fun containsAll(elements: Collection<Value>): Boolean {
+                for (value in elements) {
+                    if (!contains(value)) return false
+                }
+                return true
+            }
+
+            override fun contains(element: Value): Boolean {
+                for (index in 0 until this@IdentityArrayMap.size) {
+                    if (this@IdentityArrayMap.values[index] == element) return true
+                }
+                return false
+            }
+        }
+
+        override fun isEmpty(): Boolean = this@IdentityArrayMap.isEmpty()
+        override fun get(key: Key): Value? = this@IdentityArrayMap[key]
+        override fun containsValue(value: Value): Boolean =
+            this@IdentityArrayMap.values.contains(value)
+        override fun containsKey(key: Key): Boolean =
+            this@IdentityArrayMap[key] != null
+    }
 }
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionObserverTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionObserverTests.kt
new file mode 100644
index 0000000..b864ece8
--- /dev/null
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionObserverTests.kt
@@ -0,0 +1,561 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime
+
+import androidx.compose.runtime.mock.Text
+import androidx.compose.runtime.mock.compositionTest
+import androidx.compose.runtime.mock.expectChanges
+import androidx.compose.runtime.mock.revalidate
+import androidx.compose.runtime.mock.validate
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+@Stable
+@OptIn(ExperimentalComposeRuntimeApi::class)
+@Suppress("unused")
+class CompositionObserverTests {
+    @Test
+    fun observeScope() {
+        var startCount = 0
+        var endCount = 0
+        var disposedCount = 0
+
+        compositionTest {
+            var data by mutableStateOf(0)
+            var scope: RecomposeScope? = null
+
+            compose {
+                scope = currentRecomposeScope
+                Text("$data")
+            }
+
+            validate {
+                Text("$data")
+            }
+
+            scope?.observe(object : RecomposeScopeObserver {
+                override fun onBeginScopeComposition(scope: RecomposeScope) {
+                    startCount++
+                }
+
+                override fun onEndScopeComposition(scope: RecomposeScope) {
+                    endCount++
+                }
+
+                override fun onScopeDisposed(scope: RecomposeScope) {
+                    disposedCount++
+                }
+            })
+
+            data++
+            expectChanges()
+            revalidate()
+        }
+
+        assertEquals(1, startCount)
+        assertEquals(1, endCount)
+        assertEquals(1, disposedCount)
+    }
+
+    @Test
+    fun observeScope_dispose() {
+        var startCount = 0
+        var endCount = 0
+        var disposedCount = 0
+
+        compositionTest {
+            var data by mutableStateOf(0)
+            var scope: RecomposeScope? = null
+
+            compose {
+                scope = currentRecomposeScope
+                Text("$data")
+            }
+
+            validate {
+                Text("$data")
+            }
+
+            val handle = scope?.observe(object : RecomposeScopeObserver {
+                override fun onBeginScopeComposition(scope: RecomposeScope) {
+                    startCount++
+                }
+
+                override fun onEndScopeComposition(scope: RecomposeScope) {
+                    endCount++
+                }
+
+                override fun onScopeDisposed(scope: RecomposeScope) {
+                    disposedCount++
+                }
+            })
+
+            data++
+            expectChanges()
+            revalidate()
+
+            handle?.dispose()
+
+            data++
+            expectChanges()
+            revalidate()
+        }
+
+        assertEquals(1, startCount)
+        assertEquals(1, endCount)
+        // 0 because the observer was disposed before the scope was disposed.
+        assertEquals(0, disposedCount)
+    }
+
+    @Test
+    fun observeScope_scopeRemoved() {
+        var startCount = 0
+        var endCount = 0
+        var disposedCount = 0
+
+        compositionTest {
+            var data by mutableStateOf(0)
+            var visible by mutableStateOf(true)
+            var scope: RecomposeScope? = null
+
+            compose {
+                if (visible) {
+                    Wrap {
+                        scope = currentRecomposeScope
+                        Text("$data")
+                    }
+                }
+            }
+
+            validate {
+                if (visible) {
+                    Text("$data")
+                }
+            }
+
+            scope?.observe(object : RecomposeScopeObserver {
+                override fun onBeginScopeComposition(scope: RecomposeScope) {
+                    startCount++
+                }
+
+                override fun onEndScopeComposition(scope: RecomposeScope) {
+                    endCount++
+                }
+
+                override fun onScopeDisposed(scope: RecomposeScope) {
+                    disposedCount++
+                }
+            })
+
+            data++
+            expectChanges()
+            revalidate()
+
+            assertEquals(0, disposedCount)
+            visible = false
+            expectChanges()
+            revalidate()
+
+            assertEquals(1, disposedCount)
+        }
+
+        assertEquals(1, startCount)
+        assertEquals(1, endCount)
+        assertEquals(1, disposedCount)
+    }
+
+    @Test
+    fun observeComposition() = compositionTest {
+        var beginCount = 0
+        var endCount = 0
+        var data by mutableStateOf(0)
+        val observer = object : CompositionObserver {
+            override fun onBeginComposition(
+                composition: Composition,
+                invalidationMap: Map<RecomposeScope, Set<Any>?>
+            ) {
+                beginCount++
+            }
+
+            override fun onEndComposition(composition: Composition) {
+                endCount++
+            }
+        }
+
+        val handle = compose(observer) {
+            Text("Some composition: $data")
+        }
+
+        validate {
+            Text("Some composition: $data")
+        }
+
+        assertEquals(1, beginCount)
+        assertEquals(1, endCount)
+
+        data++
+        expectChanges()
+        revalidate()
+
+        assertEquals(2, beginCount)
+        assertEquals(2, endCount)
+
+        handle.dispose()
+
+        data++
+        expectChanges()
+        revalidate()
+
+        assertEquals(2, beginCount)
+        assertEquals(2, endCount)
+    }
+
+    @Test
+    fun observeComposition_delayedStart() = compositionTest {
+        var beginCount = 0
+        var endCount = 0
+        var data by mutableStateOf(0)
+        val observer = object : CompositionObserver {
+            override fun onBeginComposition(
+                composition: Composition,
+                invalidationMap: Map<RecomposeScope, Set<Any>?>
+            ) {
+                beginCount++
+            }
+
+            override fun onEndComposition(composition: Composition) {
+                endCount++
+            }
+        }
+        compose {
+            Text("Some composition: $data")
+        }
+
+        validate {
+            Text("Some composition: $data")
+        }
+        val handle = composition?.observe(observer)
+
+        assertEquals(0, beginCount)
+        assertEquals(0, endCount)
+
+        data++
+        expectChanges()
+        revalidate()
+
+        assertEquals(1, beginCount)
+        assertEquals(1, endCount)
+
+        handle?.dispose()
+
+        data++
+        expectChanges()
+        revalidate()
+
+        assertEquals(1, beginCount)
+        assertEquals(1, endCount)
+    }
+
+    @Test
+    fun observeComposition_observeSubcompose() = compositionTest {
+        var beginCount = 0
+        var endCount = 0
+        var data by mutableStateOf(0)
+        val compositionsSeen = mutableSetOf<Composition>()
+        val observer = object : CompositionObserver {
+            override fun onBeginComposition(
+                composition: Composition,
+                invalidationMap: Map<RecomposeScope, Set<Any>?>
+            ) {
+                compositionsSeen.add(composition)
+                beginCount++
+            }
+
+            override fun onEndComposition(composition: Composition) {
+                endCount++
+            }
+        }
+
+        var seen = data
+        val handle = compose(observer) {
+            Text("Root: $data")
+
+            TestSubcomposition {
+                seen = data
+            }
+        }
+
+        assertEquals(data, seen)
+        assertEquals(2, beginCount)
+        assertEquals(2, endCount)
+        assertEquals(2, compositionsSeen.size)
+
+        data++
+        expectChanges()
+
+        // It is valid for these to be any mutable of 2 > 4
+        assertTrue(beginCount > 4)
+        assertEquals(beginCount, endCount)
+        assertEquals(2, compositionsSeen.size)
+        val lastBeginCount = beginCount
+        val lastEndCount = endCount
+        handle.dispose()
+
+        data++
+        expectChanges()
+        assertEquals(lastBeginCount, beginCount)
+        assertEquals(lastEndCount, endCount)
+    }
+
+    @Test
+    fun observeComposition_observeSubcompose_deferred() = compositionTest {
+        var beginCount = 0
+        var endCount = 0
+        var data by mutableStateOf(0)
+        val compositionsSeen = mutableSetOf<Composition>()
+        val observer = object : CompositionObserver {
+            override fun onBeginComposition(
+                composition: Composition,
+                invalidationMap: Map<RecomposeScope, Set<Any>?>
+            ) {
+                compositionsSeen.add(composition)
+                beginCount++
+            }
+
+            override fun onEndComposition(composition: Composition) {
+                endCount++
+            }
+        }
+
+        var seen = data
+        compose {
+            Text("Root: $data")
+
+            TestSubcomposition {
+                seen = data
+            }
+        }
+
+        assertEquals(data, seen)
+        assertEquals(0, beginCount)
+        assertEquals(0, endCount)
+        assertEquals(0, compositionsSeen.size)
+
+        val handle = composition?.observe(observer)
+        data++
+        expectChanges()
+
+        // It is valid for these to be any mutable of 2 > 2
+        assertTrue(beginCount > 2)
+        assertEquals(beginCount, endCount)
+        assertEquals(2, compositionsSeen.size)
+        val lastBeginCount = beginCount
+        val lastEndCount = endCount
+
+        handle?.dispose()
+        data++
+        expectChanges()
+        assertEquals(lastBeginCount, beginCount)
+        assertEquals(lastEndCount, endCount)
+    }
+
+    @Test
+    fun observeComposition_observeSubcompose_shadowing() = compositionTest {
+        var beginCountOne = 0
+        var endCountOne = 0
+        var beginCountTwo = 0
+        var endCountTwo = 0
+        var data by mutableStateOf(0)
+        val compositionsSeen = mutableSetOf<Composition>()
+        val observer1 = object : CompositionObserver {
+            override fun onBeginComposition(
+                composition: Composition,
+                invalidationMap: Map<RecomposeScope, Set<Any>?>
+            ) {
+                compositionsSeen.add(composition)
+                beginCountOne++
+            }
+
+            override fun onEndComposition(composition: Composition) {
+                endCountOne++
+            }
+        }
+        val observer2 = object : CompositionObserver {
+            override fun onBeginComposition(
+                composition: Composition,
+                invalidationMap: Map<RecomposeScope, Set<Any>?>
+            ) {
+                beginCountTwo++
+            }
+
+            override fun onEndComposition(composition: Composition) {
+                endCountTwo++
+            }
+        }
+
+        var seen = data
+        compose {
+            Text("Root: $data")
+
+            TestSubcomposition {
+                seen = data
+
+                TestSubcomposition {
+                    seen = data
+                }
+            }
+        }
+
+        assertEquals(data, seen)
+        assertEquals(0, beginCountOne)
+        assertEquals(0, endCountOne)
+        assertEquals(0, compositionsSeen.size)
+
+        val composition = composition ?: error("No composition found")
+        val handle = composition.observe(observer1)
+        data++
+        expectChanges()
+
+        // It is valid for these to be any mutable of 2 > 2
+        assertTrue(beginCountOne > 2)
+        assertEquals(beginCountOne, endCountOne)
+        assertEquals(3, compositionsSeen.size)
+
+        val subComposition = compositionsSeen.first { it != composition }
+        val subcomposeHandle = subComposition.observe(observer2)
+
+        data++
+        expectChanges()
+        assertTrue(beginCountTwo > 0)
+        assertEquals(beginCountTwo, endCountTwo)
+        val firstBeginCountTwo = beginCountTwo
+
+        val middleCountOne = beginCountOne
+        handle.dispose()
+        data++
+        expectChanges()
+
+        // Changes for the parent have stopped
+        assertEquals(middleCountOne, beginCountOne)
+        assertEquals(middleCountOne, endCountOne)
+
+        // but changes for the sub-compositions have not
+        assertTrue(beginCountTwo > firstBeginCountTwo)
+        assertEquals(beginCountTwo, endCountTwo)
+        val middleCountTwo = beginCountTwo
+
+        // Restart the main observer
+        val handle2 = composition.observe(observer1)
+        data++
+        expectChanges()
+
+        assertTrue(beginCountOne > middleCountOne)
+        assertTrue(beginCountTwo > middleCountTwo)
+
+        val penultimateCountOne = beginCountOne
+        val lastCountTwo = beginCountTwo
+
+        // Dispose the subcompose observer
+        subcomposeHandle.dispose()
+        data++
+        expectChanges()
+
+        // Assert that we are no longer receiving changes sent to observer2
+        assertEquals(lastCountTwo, beginCountTwo)
+
+        // But we are for observer1 and it receives the sub-composition changes.
+        assertTrue(beginCountOne >= penultimateCountOne + 3)
+        val lastCountOne = beginCountOne
+
+        handle2.dispose()
+        data++
+        expectChanges()
+
+        // Assert no are sent.
+        assertEquals(lastCountOne, beginCountOne)
+        assertEquals(lastCountTwo, beginCountTwo)
+    }
+
+    @Test
+    fun observeDataChanges() = compositionTest {
+        val data = Array(4) { mutableStateOf(0) }
+        val expectedScopes = Array<RecomposeScope?>(4) { null }
+
+        compose {
+            for (i in data.indices) {
+                Wrap {
+                    Text("Data ${data[i].value}")
+                    expectedScopes[i] = currentRecomposeScope
+                }
+            }
+        }
+
+        validate {
+            for (i in data.indices) {
+                Text("Data ${data[i].value}")
+            }
+        }
+
+        // Validate that the scopes are unique
+        assertEquals(4, expectedScopes.toSet().size)
+
+        val composition = composition ?: error("No composition")
+        fun changes(vararg indexes: Int) {
+            var validatedSomething = false
+            val handle = composition.observe(
+                object : CompositionObserver {
+                    override fun onBeginComposition(
+                        composition: Composition,
+                        invalidationMap: Map<RecomposeScope, Set<Any>?>
+                    ) {
+                        validatedSomething = true
+                        for (index in indexes) {
+                            assertTrue(invalidationMap.containsKey(expectedScopes[index]))
+                        }
+                    }
+
+                    override fun onEndComposition(composition: Composition) {
+                        // Nothing to do
+                    }
+                }
+            )
+            for (index in indexes) {
+                data[index].value++
+            }
+            expectChanges()
+            assertTrue(validatedSomething)
+            handle.dispose()
+        }
+
+        changes(0)
+        changes(1)
+        changes(2)
+        changes(3)
+        changes(0, 1)
+        changes(0, 2)
+        changes(0, 3)
+        changes(1, 2)
+        changes(1, 3)
+        changes(2, 3)
+        changes(0, 1, 2)
+        changes(0, 1, 3)
+        changes(0, 2, 3)
+        changes(1, 2, 3)
+        changes(0, 1, 2, 3)
+    }
+}
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArrayMapTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArrayMapTests.kt
index 8187425..9e9cca7 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArrayMapTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/collection/IdentityArrayMapTests.kt
@@ -151,4 +151,354 @@
         assertTrue(map.isEmpty())
         assertEquals(0, map.size, "map size should be 0 after calling clear")
     }
+
+    @Test
+    fun asMap_create() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        assertNotNull(mapInterface)
+    }
+
+    @Test
+    fun asMap_entries() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val entries = mapInterface.entries
+        assertNotNull(entries)
+    }
+
+    @Test
+    fun asMap_entries_size() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val entries = mapInterface.entries
+        assertEquals(count, entries.size)
+        map.remove(keys[5])
+        assertEquals(count - 1, entries.size)
+        map.clear()
+        assertEquals(0, entries.size)
+    }
+
+    @Test
+    fun asMap_entries_isEmpty() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val entries = mapInterface.entries
+        assertFalse(entries.isEmpty())
+        map.clear()
+        assertTrue(entries.isEmpty())
+    }
+
+    @Test
+    fun asMap_entries_iterator() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val entries = mapInterface.entries
+        var counted_entries = 0
+        for ((key, value) in entries) {
+            assertEquals(map[key], value)
+            counted_entries++
+        }
+        assertEquals(count, counted_entries)
+    }
+
+    @Test
+    fun asMap_entries_containsAll() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val entries = mapInterface.entries
+        val entryInstances = entries.toList()
+        val containsAllPositive = entries.containsAll(entryInstances)
+        assertTrue(containsAllPositive)
+        map.remove(keys[5])
+        val containsAllNegative = entries.containsAll(entryInstances)
+        assertFalse(containsAllNegative)
+    }
+
+    @Test
+    fun asMap_entries_contains() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val entries = mapInterface.entries
+        val entryInstances = entries.toList()
+        val containsPositive = entries.contains(entryInstances[5])
+        assertTrue(containsPositive)
+        map.remove(keys[5])
+        val entryFive = entryInstances.first { it.key == keys[5] }
+        val containsNegative = entries.contains(entryFive)
+        assertFalse(containsNegative)
+    }
+
+    @Test
+    fun asMap_keys() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val keys = mapInterface.keys
+        assertNotNull(keys)
+    }
+
+    @Test
+    fun asMap_keys_size() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val iKeys = mapInterface.keys
+        assertEquals(count, iKeys.size)
+        map.remove(keys[5])
+        assertEquals(count - 1, iKeys.size)
+    }
+
+    @Test
+    fun asMap_keys_isEmpty() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val keys = mapInterface.keys
+        assertFalse(keys.isEmpty())
+        map.clear()
+        assertTrue(keys.isEmpty())
+    }
+
+    @Test
+    fun asMap_keys_iterator() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val keys = mapInterface.keys
+        var counted_keys = 0
+        for (key in keys) {
+            assertTrue(map.contains(key))
+            counted_keys++
+        }
+        assertEquals(count, counted_keys)
+    }
+
+    @Test
+    fun asMap_keys_containsAll() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val iKeys = mapInterface.keys
+        val keyInstances = iKeys.toList()
+        assertTrue(iKeys.containsAll(keyInstances))
+        map.remove(keys[5])
+        assertFalse(iKeys.containsAll(keyInstances))
+    }
+
+    @Test
+    fun asMap_keys_contains() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val iKeys = mapInterface.keys
+        assertTrue(iKeys.contains(keys[5]))
+        map.remove(keys[5])
+        assertFalse(iKeys.contains(keys[5]))
+    }
+
+    @Test
+    fun asMap_size() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        assertEquals(count, mapInterface.size)
+        map.remove(keys[5])
+        assertEquals(count - 1, mapInterface.size)
+    }
+
+    @Test
+    fun asMap_values() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val values = mapInterface.values
+        assertNotNull(values)
+    }
+
+    @Test
+    fun asMap_values_size() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val values = mapInterface.values
+        assertEquals(count, values.size)
+        map.remove(keys[5])
+        assertEquals(count - 1, values.size)
+        map.clear()
+        assertEquals(0, values.size)
+    }
+
+    @Test
+    fun asMap_values_isEmpty() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val values = mapInterface.values
+        assertFalse(values.isEmpty())
+        map.clear()
+        assertTrue(values.isEmpty())
+    }
+
+    @Test
+    fun asMap_values_iterator() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val values = mapInterface.values
+        var countedValues = 0
+        for (value in values) {
+            var found = false
+            map.forEach { _, mapValue ->
+                if (value == mapValue) found = true
+            }
+            assertTrue(found)
+            countedValues++
+        }
+        assertEquals(count, countedValues)
+    }
+
+    @Test
+    fun asMap_values_containsAll() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val values = mapInterface.values
+        val valueInstances = values.toList()
+        assertTrue(values.containsAll(valueInstances))
+        map.remove(keys[5])
+        assertFalse(values.containsAll(valueInstances))
+    }
+
+    @Test
+    fun asMap_values_contains() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        val values = mapInterface.values
+        assertTrue(values.contains("5"))
+        map.remove(keys[5])
+        assertFalse(values.contains("5"))
+    }
+
+    @Test
+    fun asMap_isEmpty() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        assertFalse(mapInterface.isEmpty())
+        map.clear()
+        assertTrue(mapInterface.isEmpty())
+    }
+
+    @Test
+    fun asMap_get() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        for (key in keys.take(count)) {
+            assertEquals(map[key], mapInterface[key])
+        }
+    }
+
+    @Test
+    fun asMap_containsValue() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        for (i in 0 until count) {
+            assertTrue(mapInterface.containsValue(i.toString()))
+        }
+        assertFalse(mapInterface.containsValue("not there"))
+    }
+
+    @Test
+    fun asMap_containsKey() {
+        val count = 16
+        val map = IdentityArrayMap<Key, String>()
+        repeat(count) {
+            map[keys[it]] = it.toString()
+        }
+        val mapInterface = map.asMap()
+        repeat(count) {
+            assertTrue(mapInterface.containsKey(keys[it]))
+        }
+        assertFalse(mapInterface.containsKey(keys[count]))
+    }
 }
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/mock/CompositionTest.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
index 4a3d14b..12de432 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
@@ -18,9 +18,13 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
+import androidx.compose.runtime.CompositionObserver
+import androidx.compose.runtime.CompositionObserverHandle
 import androidx.compose.runtime.ControlledComposition
+import androidx.compose.runtime.ExperimentalComposeRuntimeApi
 import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.Recomposer
+import androidx.compose.runtime.observe
 import androidx.compose.runtime.snapshots.Snapshot
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
@@ -58,6 +62,21 @@
                 composition.setContent(block)
             }
 
+            @OptIn(ExperimentalComposeRuntimeApi::class)
+            override fun compose(
+                observer: CompositionObserver,
+                block: @Composable () -> Unit
+            ): CompositionObserverHandle {
+                check(!composed) { "Compose should only be called once" }
+                composed = true
+                root = View().apply { name = "root" }
+                val composition = Composition(ViewApplier(root), recomposer)
+                val result = composition.observe(observer)
+                this.composition = composition
+                composition.setContent(block)
+                return result
+            }
+
             override fun advanceCount(ignorePendingWork: Boolean): Long {
                 val changeCount = recomposer.changeCount
                 Snapshot.sendApplyNotifications()
@@ -104,6 +123,16 @@
     fun compose(block: @Composable () -> Unit)
 
     /**
+     * Compose a block observed using the mock view composer.
+     */
+
+    @OptIn(ExperimentalComposeRuntimeApi::class)
+    fun compose(
+        observer: CompositionObserver,
+        block: @Composable () -> Unit
+    ): CompositionObserverHandle
+
+    /**
      * Advance the state which executes any pending compositions, if any. Returns true if
      * advancing resulted in changes being applied.
      */
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
index 9d6af43..2c60ee5 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
@@ -96,9 +96,7 @@
     )
     // TODO(b/183448615) @InternalTextApi
     fun showSoftwareKeyboard() {
-        if (_currentInputSession.get() != null) {
-            platformTextInputService.showSoftwareKeyboard()
-        }
+        platformTextInputService.showSoftwareKeyboard()
     }
 
     /**
diff --git a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextInputServiceTest.kt b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextInputServiceTest.kt
index f5576cf..3f4880a 100644
--- a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextInputServiceTest.kt
+++ b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextInputServiceTest.kt
@@ -180,7 +180,7 @@
         ).dispose()
 
         textInputService.showSoftwareKeyboard()
-        verify(platformService, never()).showSoftwareKeyboard()
+        verify(platformService).showSoftwareKeyboard()
     }
 
     @Test
@@ -190,7 +190,7 @@
         val textInputService = TextInputService(platformService)
 
         textInputService.showSoftwareKeyboard()
-        verify(platformService, never()).showSoftwareKeyboard()
+        verify(platformService).showSoftwareKeyboard()
     }
 
     @Test
diff --git a/compose/ui/ui-tooling-data/OWNERS b/compose/ui/ui-tooling-data/OWNERS
index 583d415..f09a8f7 100644
--- a/compose/ui/ui-tooling-data/OWNERS
+++ b/compose/ui/ui-tooling-data/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 378604
 chuckj@google.com
 diegoperez@google.com
 jlauridsen@google.com
\ No newline at end of file
diff --git a/compose/ui/ui-tooling-preview/OWNERS b/compose/ui/ui-tooling-preview/OWNERS
index 7e888c71..4ebd731 100644
--- a/compose/ui/ui-tooling-preview/OWNERS
+++ b/compose/ui/ui-tooling-preview/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 378604
 amaurym@google.com
 diegoperez@google.com
\ No newline at end of file
diff --git a/compose/ui/ui-tooling/OWNERS b/compose/ui/ui-tooling/OWNERS
index 32894f6..07aab4a 100644
--- a/compose/ui/ui-tooling/OWNERS
+++ b/compose/ui/ui-tooling/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 378604
 chuckj@google.com
 diegoperez@google.com
 jlauridsen@google.com
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 2a94d3c..6484a78 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2175,20 +2175,13 @@
   @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public sealed interface LookaheadLayoutCoordinates extends androidx.compose.ui.layout.LayoutCoordinates {
   }
 
-  @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public interface LookaheadLayoutScope {
-    method @Deprecated public androidx.compose.ui.Modifier onPlaced(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.LookaheadLayoutCoordinates,? super androidx.compose.ui.layout.LookaheadLayoutCoordinates,kotlin.Unit> onPlaced);
-  }
-
   @androidx.compose.ui.ExperimentalComposeUiApi public interface LookaheadScope {
     method public androidx.compose.ui.layout.LayoutCoordinates getLookaheadScopeCoordinates(androidx.compose.ui.layout.Placeable.PlacementScope);
-    method @Deprecated public default androidx.compose.ui.Modifier intermediateLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super androidx.compose.ui.unit.IntSize,? extends androidx.compose.ui.layout.MeasureResult> measure);
     method public default long localLookaheadPositionOf(androidx.compose.ui.layout.LayoutCoordinates, androidx.compose.ui.layout.LayoutCoordinates coordinates);
-    method @Deprecated public androidx.compose.ui.Modifier onPlaced(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.LookaheadLayoutCoordinates,? super androidx.compose.ui.layout.LookaheadLayoutCoordinates,kotlin.Unit> onPlaced);
     method public androidx.compose.ui.layout.LayoutCoordinates toLookaheadCoordinates(androidx.compose.ui.layout.LayoutCoordinates);
   }
 
   public final class LookaheadScopeKt {
-    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.UiComposable public static void LookaheadLayout(kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LookaheadScope,kotlin.Unit> content, optional androidx.compose.ui.Modifier modifier, androidx.compose.ui.layout.MeasurePolicy measurePolicy);
     method @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.UiComposable public static void LookaheadScope(kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LookaheadScope,kotlin.Unit> content);
     method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier intermediateLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntermediateMeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measure);
   }
@@ -2709,6 +2702,7 @@
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.input.InputModeManager> getLocalInputModeManager();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.LayoutDirection> getLocalLayoutDirection();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.input.PlatformTextInputPluginRegistry> getLocalPlatformTextInputPluginRegistry();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.SoftwareKeyboardController> getLocalSoftwareKeyboardController();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.input.TextInputService> getLocalTextInputService();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.TextToolbar> getLocalTextToolbar();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.UriHandler> getLocalUriHandler();
@@ -2725,6 +2719,7 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.input.InputModeManager> LocalInputModeManager;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.LayoutDirection> LocalLayoutDirection;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.input.PlatformTextInputPluginRegistry> LocalPlatformTextInputPluginRegistry;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.SoftwareKeyboardController> LocalSoftwareKeyboardController;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.input.TextInputService> LocalTextInputService;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.TextToolbar> LocalTextToolbar;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.UriHandler> LocalUriHandler;
@@ -2795,22 +2790,13 @@
     property public Object? valueOverride;
   }
 
-  @androidx.compose.ui.ExperimentalComposeUiApi public final class LocalSoftwareKeyboardController {
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.platform.SoftwareKeyboardController? getCurrent();
-    method public infix androidx.compose.runtime.ProvidedValue<androidx.compose.ui.platform.SoftwareKeyboardController> provides(androidx.compose.ui.platform.SoftwareKeyboardController softwareKeyboardController);
-    property @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi public final androidx.compose.ui.platform.SoftwareKeyboardController? current;
-    field public static final androidx.compose.ui.platform.LocalSoftwareKeyboardController INSTANCE;
-  }
-
   public final class NestedScrollInteropConnectionKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.ui.input.nestedscroll.NestedScrollConnection rememberNestedScrollInteropConnection(optional android.view.View hostView);
   }
 
-  @androidx.compose.runtime.Stable @androidx.compose.ui.ExperimentalComposeUiApi public interface SoftwareKeyboardController {
+  @androidx.compose.runtime.Stable public interface SoftwareKeyboardController {
     method public void hide();
-    method @Deprecated public default void hideSoftwareKeyboard();
     method public void show();
-    method @Deprecated public default void showSoftwareKeyboard();
   }
 
   public final class TestTagKt {
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 9de3a65..93f9919 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2178,20 +2178,13 @@
   @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public sealed interface LookaheadLayoutCoordinates extends androidx.compose.ui.layout.LayoutCoordinates {
   }
 
-  @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public interface LookaheadLayoutScope {
-    method @Deprecated public androidx.compose.ui.Modifier onPlaced(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.LookaheadLayoutCoordinates,? super androidx.compose.ui.layout.LookaheadLayoutCoordinates,kotlin.Unit> onPlaced);
-  }
-
   @androidx.compose.ui.ExperimentalComposeUiApi public interface LookaheadScope {
     method public androidx.compose.ui.layout.LayoutCoordinates getLookaheadScopeCoordinates(androidx.compose.ui.layout.Placeable.PlacementScope);
-    method @Deprecated public default androidx.compose.ui.Modifier intermediateLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super androidx.compose.ui.unit.IntSize,? extends androidx.compose.ui.layout.MeasureResult> measure);
     method public default long localLookaheadPositionOf(androidx.compose.ui.layout.LayoutCoordinates, androidx.compose.ui.layout.LayoutCoordinates coordinates);
-    method @Deprecated public androidx.compose.ui.Modifier onPlaced(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.LookaheadLayoutCoordinates,? super androidx.compose.ui.layout.LookaheadLayoutCoordinates,kotlin.Unit> onPlaced);
     method public androidx.compose.ui.layout.LayoutCoordinates toLookaheadCoordinates(androidx.compose.ui.layout.LayoutCoordinates);
   }
 
   public final class LookaheadScopeKt {
-    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.UiComposable public static void LookaheadLayout(kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LookaheadScope,kotlin.Unit> content, optional androidx.compose.ui.Modifier modifier, androidx.compose.ui.layout.MeasurePolicy measurePolicy);
     method @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.UiComposable public static void LookaheadScope(kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LookaheadScope,kotlin.Unit> content);
     method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier intermediateLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntermediateMeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measure);
   }
@@ -2762,6 +2755,7 @@
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.input.InputModeManager> getLocalInputModeManager();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.LayoutDirection> getLocalLayoutDirection();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.input.PlatformTextInputPluginRegistry> getLocalPlatformTextInputPluginRegistry();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.SoftwareKeyboardController> getLocalSoftwareKeyboardController();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.input.TextInputService> getLocalTextInputService();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.TextToolbar> getLocalTextToolbar();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.UriHandler> getLocalUriHandler();
@@ -2778,6 +2772,7 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.input.InputModeManager> LocalInputModeManager;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.LayoutDirection> LocalLayoutDirection;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.input.PlatformTextInputPluginRegistry> LocalPlatformTextInputPluginRegistry;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.SoftwareKeyboardController> LocalSoftwareKeyboardController;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.input.TextInputService> LocalTextInputService;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.TextToolbar> LocalTextToolbar;
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.UriHandler> LocalUriHandler;
@@ -2849,22 +2844,13 @@
     property public Object? valueOverride;
   }
 
-  @androidx.compose.ui.ExperimentalComposeUiApi public final class LocalSoftwareKeyboardController {
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.platform.SoftwareKeyboardController? getCurrent();
-    method public infix androidx.compose.runtime.ProvidedValue<androidx.compose.ui.platform.SoftwareKeyboardController> provides(androidx.compose.ui.platform.SoftwareKeyboardController softwareKeyboardController);
-    property @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi public final androidx.compose.ui.platform.SoftwareKeyboardController? current;
-    field public static final androidx.compose.ui.platform.LocalSoftwareKeyboardController INSTANCE;
-  }
-
   public final class NestedScrollInteropConnectionKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.ui.input.nestedscroll.NestedScrollConnection rememberNestedScrollInteropConnection(optional android.view.View hostView);
   }
 
-  @androidx.compose.runtime.Stable @androidx.compose.ui.ExperimentalComposeUiApi public interface SoftwareKeyboardController {
+  @androidx.compose.runtime.Stable public interface SoftwareKeyboardController {
     method public void hide();
-    method @Deprecated public default void hideSoftwareKeyboard();
     method public void show();
-    method @Deprecated public default void showSoftwareKeyboard();
   }
 
   public final class TestTagKt {
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadLayoutSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadScopeSample.kt
similarity index 99%
rename from compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadLayoutSample.kt
rename to compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadScopeSample.kt
index 6d4e513..a7086c8 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadLayoutSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadScopeSample.kt
@@ -198,7 +198,7 @@
                 .fillMaxSize()
                 .clickable { isInColumn = !isInColumn }
         ) {
-            // As the items get moved between Column and Row, their positions in LookaheadLayout
+            // As the items get moved between Column and Row, their positions in LookaheadScope
             // will change. The `animatePlacementInScope` modifier created above will
             // observe that final position change via `localLookaheadPositionOf`, and create
             // a position animation.
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
index 7d49b02..ef2b7a4 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Active
+import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -119,6 +120,92 @@
         rule.runOnIdle { assertThat(focusStates).isExactly(Active) }
     }
 
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun whenFocusMovesWithinParent_onFocusEventIsNotCalled() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        val (item1, item2) = FocusRequester.createRefs()
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .focusRequester(item1)
+                        .focusTarget()
+                )
+                Box(
+                    modifier = Modifier
+                        .focusRequester(item2)
+                        .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle {
+            item1.requestFocus()
+            focusStates.clear()
+        }
+
+        // Act.
+        rule.runOnIdle { item2.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isEmpty() }
+    }
+
+    @Test
+    fun whenFocusIsGained_onFocusEventIsCalledOnParent() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .focusRequester(focusRequester)
+                        .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isExactly(ActiveParent) }
+    }
+
+    @Test
+    fun whenFocusIsGained_onFocusEventIsCalledOnLocalParent() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .focusTarget()
+                    .focusRequester(focusRequester)
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isExactly(ActiveParent) }
+    }
+
     @Test
     fun requestingFocusWhenAlreadyFocused_onFocusEventIsCalledAgain() {
         // Arrange.
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 6e3afac..acd31fd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -288,7 +288,7 @@
     }
 
     @Test
-    fun nestedLookaheadLayoutTest() {
+    fun nestedLookaheadScopeTest() {
         var parentLookaheadMeasure = 0
         var childLookaheadMeasure = 0
         var parentLookaheadPlace = 0
@@ -550,7 +550,7 @@
             assertEquals(0, measure)
             assertEquals(0, place)
 
-            // Switch to LookaheadLayout
+            // Switch to LookaheadScope
             controlGroupEnabled = !controlGroupEnabled
         }
 
@@ -1243,7 +1243,7 @@
     }
 
     @Test
-    fun firstBaselineAlignmentInLookaheadLayout() {
+    fun firstBaselineAlignmentInLookaheadScope() {
         assertSameLayoutWithAndWithoutLookahead { modifier ->
             Box(modifier.fillMaxWidth()) {
                 Row {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureInPlacementTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureInPlacementTest.kt
index 6f18534..2895fac 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureInPlacementTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureInPlacementTest.kt
@@ -114,7 +114,7 @@
      */
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
-    fun measureInModifierPlacementWithLookaheadLayout() {
+    fun measureInModifierPlacementWithLookaheadScope() {
         var childSize = IntSize.Zero
         rule.setContent {
             LookaheadScope {
@@ -146,7 +146,7 @@
      */
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
-    fun measureInLayoutPlacementWithLookaheadLayout() {
+    fun measureInLayoutPlacementWithLookaheadScope() {
         var childSize = IntSize.Zero
         rule.setContent {
             LookaheadScope {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt
index ca5805c..7fa8477 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt
@@ -124,7 +124,7 @@
      */
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
-    fun coordinatesWhilePlacingWithLookaheadLayout() {
+    fun coordinatesWhilePlacingWithLookaheadScope() {
         val locations = mutableStateListOf<LayoutCoordinates?>()
         var locationAtPlace: IntOffset? by mutableStateOf(null)
         var boxSize by mutableStateOf(IntSize.Zero)
@@ -209,7 +209,7 @@
      */
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
-    fun coordinatesWhileAligningWithLookaheadLayout() {
+    fun coordinatesWhileAligningWithLookaheadScope() {
         val locations = mutableStateListOf<LayoutCoordinates?>()
         rule.setContent {
             LookaheadScope {
@@ -275,7 +275,7 @@
      */
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
-    fun coordinatesWhileAligningInLookaheadLayout() {
+    fun coordinatesWhileAligningInLookaheadScope() {
         val locations = mutableStateListOf<LayoutCoordinates?>()
         rule.setContent {
             LookaheadScope {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/LocalSoftwareKeyboardControllerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/LocalSoftwareKeyboardControllerTest.kt
index f3c9189..5cb4ef5 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/LocalSoftwareKeyboardControllerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/LocalSoftwareKeyboardControllerTest.kt
@@ -28,14 +28,12 @@
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.text.input.PlatformTextInputService
-import androidx.compose.ui.text.input.TextInputService
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 
@@ -81,16 +79,12 @@
     @Test
     fun localSoftwareKeybardController_delegatesTo_textInputService() {
         val platformTextInputService = mock<PlatformTextInputService>()
-        val textInputService = TextInputService(platformTextInputService)
+        platformTextInputServiceInterceptor = { platformTextInputService }
 
         rule.setContent {
-            CompositionLocalProvider(
-                LocalTextInputService provides textInputService
-            ) {
-                val controller = LocalSoftwareKeyboardController.current
-                SideEffect {
-                    controller?.hide()
-                }
+            val controller = LocalSoftwareKeyboardController.current
+            SideEffect {
+                controller?.hide()
             }
         }
 
@@ -109,26 +103,4 @@
         keyboardController!!.show()
         keyboardController!!.hide()
     }
-
-    @Test
-    fun showAndHide_noOp_whenProvidedMock() {
-        val mockSoftwareKeyboardController: SoftwareKeyboardController = mock()
-        val platformTextInputService = mock<PlatformTextInputService>()
-        val textInputService = TextInputService(platformTextInputService)
-        var controller: SoftwareKeyboardController? = null
-        rule.setContent {
-            CompositionLocalProvider(
-                LocalSoftwareKeyboardController provides mockSoftwareKeyboardController,
-                LocalTextInputService provides textInputService
-            ) {
-                controller = LocalSoftwareKeyboardController.current
-            }
-        }
-        rule.runOnIdle {
-            controller?.show()
-            controller?.hide()
-        }
-        verify(platformTextInputService, never()).hideSoftwareKeyboard()
-        verify(platformTextInputService, never()).showSoftwareKeyboard()
-    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index d4c3cd3..9526daf 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -139,6 +139,7 @@
 import androidx.compose.ui.text.font.createFontFamilyResolver
 import androidx.compose.ui.text.input.AndroidTextInputServicePlugin
 import androidx.compose.ui.text.input.PlatformTextInputPluginRegistryImpl
+import androidx.compose.ui.text.input.PlatformTextInputService
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
@@ -165,6 +166,12 @@
 import kotlin.coroutines.CoroutineContext
 import kotlin.math.roundToInt
 
+/**
+ * Allows tests to inject a custom [PlatformTextInputService].
+ */
+internal var platformTextInputServiceInterceptor:
+        (PlatformTextInputService) -> PlatformTextInputService = { it }
+
 @SuppressLint("ViewConstructor", "VisibleForTests")
 @OptIn(ExperimentalComposeUiApi::class, InternalTextApi::class, ExperimentalTextApi::class)
 internal class AndroidComposeView(context: Context, coroutineContext: CoroutineContext) :
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/AndroidTextInputServicePlugin.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/AndroidTextInputServicePlugin.kt
index befbdba..d1091e5 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/AndroidTextInputServicePlugin.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/AndroidTextInputServicePlugin.kt
@@ -21,6 +21,7 @@
 import android.view.View
 import android.view.inputmethod.EditorInfo
 import android.view.inputmethod.InputConnection
+import androidx.compose.ui.platform.platformTextInputServiceInterceptor
 import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.input.AndroidTextInputServicePlugin.Adapter
 
@@ -42,7 +43,8 @@
 
     override fun createAdapter(platformTextInput: PlatformTextInput, view: View): Adapter {
         val platformService = TextInputServiceAndroid(view, platformTextInput)
-        return Adapter(TextInputService(platformService), platformService)
+        val interceptedService = platformTextInputServiceInterceptor(platformService)
+        return Adapter(TextInputService(interceptedService), platformService)
     }
 
     class Adapter(
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
index e653e80..cf4d24f1 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
@@ -54,21 +54,26 @@
  * custom focus [enter][FocusProperties.enter] and [exit][FocusProperties.exit]
  * [properties][FocusProperties] have been specified.
  */
-internal fun FocusTargetNode.performRequestFocus(): Boolean = when (focusState) {
-    Active, Captured -> {
-        // There is no change in focus state, but we send a focus event to notify the user
-        // that the focus request is completed.
-        refreshFocusEventNodes()
-        true
-    }
-    ActiveParent -> (clearChildFocus() && grantFocus()).also { success ->
-        if (success) refreshFocusEventNodes()
-    }
-    Inactive -> nearestAncestor(FocusTarget)
-        ?.requestFocusForChild(this)
-        ?: (requestFocusForOwner() && grantFocus()).also { success ->
-            if (success) refreshFocusEventNodes()
+internal fun FocusTargetNode.performRequestFocus(): Boolean {
+   val success = when (focusState) {
+        Active, Captured -> true
+        ActiveParent -> clearChildFocus() && grantFocus()
+        Inactive -> {
+            val parent = nearestAncestor(FocusTarget)
+            if (parent != null) {
+                val prevState = parent.focusState
+                val success = parent.requestFocusForChild(this)
+                if (success && prevState !== parent.focusState) {
+                    parent.refreshFocusEventNodes()
+                }
+                success
+            } else {
+                requestFocusForOwner() && grantFocus()
+            }
         }
+    }
+    if (success) refreshFocusEventNodes()
+    return success
 }
 
 /**
@@ -192,19 +197,13 @@
     return when (focusState) {
         // If this node is [Active], it can give focus to the requesting child.
         Active -> childNode.grantFocus().also { success ->
-            if (success) {
-                focusState = ActiveParent
-                childNode.refreshFocusEventNodes()
-                refreshFocusEventNodes()
-            }
+            if (success) focusState = ActiveParent
         }
         // If this node is [ActiveParent] ie, one of the parent's descendants is [Active],
         // remove focus from the currently focused child and grant it to the requesting child.
         ActiveParent -> {
             requireActiveChild()
-            (clearChildFocus() && childNode.grantFocus()).also { success ->
-                if (success) childNode.refreshFocusEventNodes()
-            }
+            clearChildFocus() && childNode.grantFocus()
         }
         // If this node is not [Active], we must gain focus first before granting it
         // to the requesting child.
@@ -214,18 +213,18 @@
                 // If this node is the root, request focus from the compose owner.
                 focusParent == null && requestFocusForOwner() -> {
                     focusState = Active
-                    refreshFocusEventNodes()
                     requestFocusForChild(childNode)
                 }
                 // For non-root nodes, request focus for this node before the child.
                 // We request focus even if this is a deactivated node, as we will end up taking
                 // focus away and granting it to the child.
                 focusParent != null && focusParent.requestFocusForChild(this) -> {
-                    requestFocusForChild(childNode).also {
+                    requestFocusForChild(childNode).also { success ->
                         // Verify that focus state was granted to the child.
                         // If this child didn't take focus then we can end up in a situation where
                         // a deactivated parent is focused.
                         check(this.focusState == ActiveParent) { "Deactivated node is focused" }
+                        if (success) focusParent.refreshFocusEventNodes()
                     }
                 }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt
index 2482c76..19fcadb 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt
@@ -282,21 +282,6 @@
                 this@lookaheadScopeCoordinates.lookaheadScopeCoordinates
             }
 
-        @Suppress("DEPRECATION")
-        @Deprecated(
-            "onPlaced in LookaheadLayoutScope has been deprecated. It's replaced" +
-                " with reading LookaheadLayoutCoordinates directly during placement in" +
-                "IntermediateMeasureScope"
-        )
-        override fun Modifier.onPlaced(
-            onPlaced: (
-                lookaheadScopeCoordinates: LookaheadLayoutCoordinates,
-                layoutCoordinates: LookaheadLayoutCoordinates
-            ) -> Unit
-        ): Modifier = with(closestLookaheadScope) {
-            this@onPlaced.onPlaced(onPlaced)
-        }
-
         override fun layout(
             width: Int,
             height: Int,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadScope.kt
index ccdd77b..949cb76 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadScope.kt
@@ -32,67 +32,6 @@
 import androidx.compose.ui.unit.IntSize
 import kotlinx.coroutines.CoroutineScope
 
-@Suppress("ComposableLambdaParameterPosition")
-@Deprecated(
-    "LookaheadLayout has been replaced with LookaheadScope that does not require" +
-        " a Modifier or a MeasurePolicy.",
-    replaceWith = ReplaceWith(
-        "LookaheadScope { Layout(content = { content() }, \n" +
-            " modifier = modifier, measurePolicy = measurePolicy) }"
-    )
-)
-@ExperimentalComposeUiApi
-@UiComposable
-@Composable
-fun LookaheadLayout(
-    content: @Composable @UiComposable LookaheadScope.() -> Unit,
-    modifier: Modifier = Modifier,
-    measurePolicy: MeasurePolicy
-) {
-    LookaheadScope {
-        Layout(
-            content = { content() },
-            modifier = modifier,
-            measurePolicy = measurePolicy
-        )
-    }
-}
-
-/**
- * [LookaheadLayoutScope] provides a receiver scope for all (direct and indirect) child layouts in
- * [LookaheadLayout]. In [LookaheadLayoutScope], the measurement and placement of any layout
- * calculated in the lookahead pass can be observed via [Modifier.intermediateLayout] and
- * [Modifier.onPlaced] respectively.
- *
- * @sample androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample
- */
-@Deprecated(
-    "LookaheadLayoutScope has been renamed to LookaheadScope",
-    ReplaceWith("LookaheadScope")
-)
-@ExperimentalComposeUiApi
-interface LookaheadLayoutScope {
-    @Deprecated(
-        "onPlaced in LookaheadLayoutScope has been deprecated. It's replaced" +
-            " with reading LookaheadLayoutCoordinates directly during placement in" +
-            " IntermediateMeasureScope. See example below."
-    )
-    /**
-     * [onPlaced] gets invoked after the parent [LayoutModifier] has been placed
-     * and before child [LayoutModifier] is placed. This allows child [LayoutModifier] to adjust
-     * its own placement based on its parent.
-     *
-     * @sample androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample
-     */
-    @Suppress("DEPRECATION")
-    fun Modifier.onPlaced(
-        onPlaced: (
-            lookaheadScopeCoordinates: LookaheadLayoutCoordinates,
-            layoutCoordinates: LookaheadLayoutCoordinates
-        ) -> Unit
-    ): Modifier
-}
-
 /**
  * [LookaheadScope] starts a scope in which all layouts scope will receive a lookahead pass
  * preceding the main measure/layout pass. This lookahead pass will calculate the layout
@@ -231,63 +170,6 @@
             coordinates.toLookaheadCoordinates(),
             Offset.Zero
         )
-
-    @Suppress("DEPRECATION")
-    @Deprecated(
-        "onPlaced in LookaheadLayoutScope has been deprecated. It's replaced" +
-            " with reading LookaheadLayoutCoordinates directly during placement in" +
-            " IntermediateMeasureScope. See example below."
-    )
-        /**
-         * [onPlaced] gets invoked after the parent [LayoutModifier] has been placed
-         * and before child [LayoutModifier] is placed. This allows child [LayoutModifier] to adjust
-         * its own placement based on its parent.
-         *
-         * [onPlaced] callback will be invoked with the [LookaheadLayoutCoordinates] of the LayoutNode
-         * emitted by [LookaheadLayout] as the first parameter, and the [LookaheadLayoutCoordinates] of
-         * this modifier as the second parameter. Given the [LookaheadLayoutCoordinates]s, both
-         * lookahead position and current position of this modifier in the [LookaheadLayout]'s
-         * coordinates system can be calculated using
-         * [LookaheadLayoutCoordinates.localLookaheadPositionOf] and
-         * [LookaheadLayoutCoordinates.localPositionOf], respectively.
-         *
-         * @sample androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample
-         */
-    fun Modifier.onPlaced(
-        onPlaced: (
-            lookaheadScopeCoordinates: LookaheadLayoutCoordinates,
-            layoutCoordinates: LookaheadLayoutCoordinates
-        ) -> Unit
-    ): Modifier
-
-    @Deprecated(
-        "",
-        ReplaceWith(
-            "intermediateLayout { measurable, constraints ->" +
-                "measure.invoke(this, measurable, constraints, lookaheadSize)" +
-                "}"
-        )
-    )
-        /**
-         * Creates an intermediate layout based on target size of the child layout calculated
-         * in the lookahead. This allows the intermediate layout to morph the child layout
-         * after lookahead through [measure], in which the size of the child layout calculated from the
-         * lookahead is provided. [intermediateLayout] does _not_ participate in the lookahead. It is
-         * only invoked for retroactively changing the layout based on the lookahead before the layout
-         * is drawn.
-         *
-         * @sample androidx.compose.ui.samples.IntermediateLayoutSample
-         */
-    fun Modifier.intermediateLayout(
-        measure: MeasureScope.(
-            measurable: Measurable,
-            constraints: Constraints,
-            lookaheadSize: IntSize
-        ) -> MeasureResult,
-    ): Modifier =
-        this.intermediateLayout { measurable: Measurable, constraints: Constraints ->
-            measure(measurable, constraints, lookaheadSize)
-        }
 }
 
 @OptIn(ExperimentalComposeUiApi::class)
@@ -305,22 +187,4 @@
 
     override val Placeable.PlacementScope.lookaheadScopeCoordinates: LayoutCoordinates
         get() = scopeCoordinates!!()
-
-    @Suppress("DEPRECATION")
-    @Deprecated(
-        "onPlaced in LookaheadLayoutScope has been deprecated. It's replaced" +
-            " with reading LookaheadLayoutCoordinates directly during placement in" +
-            "IntermediateMeasureScope"
-    )
-    override fun Modifier.onPlaced(
-        onPlaced: (
-            lookaheadScopeCoordinates: LookaheadLayoutCoordinates,
-            layoutCoordinates: LookaheadLayoutCoordinates
-        ) -> Unit
-    ): Modifier = this.onPlaced { coordinates ->
-        onPlaced(
-            scopeCoordinates!!().toLookaheadCoordinates() as LookaheadLayoutCoordinates,
-            coordinates.toLookaheadCoordinates() as LookaheadLayoutCoordinates
-        )
-    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index a8e56fc..fc26143 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -1012,7 +1012,7 @@
     ) {
         check(lookaheadRoot != null) {
             "Lookahead measure cannot be requested on a node that is not a part of the" +
-                "LookaheadLayout"
+                "LookaheadScope"
         }
         val owner = owner ?: return
         if (!ignoreRemeasureRequests && !isVirtual) {
@@ -1027,7 +1027,7 @@
     }
 
     /**
-     * This gets called when both lookahead measurement (if in a LookaheadLayout) and actual
+     * This gets called when both lookahead measurement (if in a LookaheadScope) and actual
      * measurement need to be re-done. Such events include modifier change, attach/detach, etc.
      */
     internal fun invalidateMeasurements() {
@@ -1395,7 +1395,7 @@
     /**
      * Describes the current state the [LayoutNode] is in. A [LayoutNode] is expected to be in
      * [LookaheadMeasuring] first, followed by [LookaheadLayingOut] if it is in a
-     * LookaheadLayout. After the lookahead is finished, [Measuring] and then [LayingOut] will
+     * LookaheadScope. After the lookahead is finished, [Measuring] and then [LayingOut] will
      * happen as needed.
      */
     internal enum class LayoutState {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
index 5954d8d..a4c270d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
@@ -120,7 +120,7 @@
      * Requests lookahead remeasure for this [layoutNode] and nodes affected by its measure result
      *
      * Note: This should only be called on a [LayoutNode] in the subtree defined in a
-     * LookaheadLayout. The caller is responsible for checking with [LayoutNode.lookaheadRoot]
+     * LookaheadScope. The caller is responsible for checking with [LayoutNode.lookaheadRoot]
      * is valid (i.e. non-null) before calling this method.
      *
      * @return true if the [measureAndLayout] execution should be scheduled as a result
@@ -129,7 +129,7 @@
     fun requestLookaheadRemeasure(layoutNode: LayoutNode, forced: Boolean = false): Boolean {
         check(layoutNode.lookaheadRoot != null) {
             "Error: requestLookaheadRemeasure cannot be called on a node outside" +
-                " LookaheadLayout"
+                " LookaheadScope"
         }
         return when (layoutNode.layoutState) {
             LookaheadMeasuring -> {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
index 426e1f7..dcf0654 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
@@ -30,6 +30,8 @@
 import androidx.compose.ui.modifier.ModifierLocalManager
 import androidx.compose.ui.platform.AccessibilityManager
 import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.DelegatingSoftwareKeyboardController
+import androidx.compose.ui.platform.SoftwareKeyboardController
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.WindowInfo
@@ -111,6 +113,9 @@
 
     val textInputService: TextInputService
 
+    val softwareKeyboardController: SoftwareKeyboardController
+        get() = DelegatingSoftwareKeyboardController(textInputService)
+
     val platformTextInputPluginRegistry: PlatformTextInputPluginRegistry
 
     val pointerIconService: PointerIconService
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt
index 689bc64..4a23ed0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt
@@ -63,7 +63,7 @@
         }
     }
 
-    private val onCommitAffectingLookaheadLayout: (LayoutNode) -> Unit = { layoutNode ->
+    private val onCommitAffectingLookahead: (LayoutNode) -> Unit = { layoutNode ->
         if (layoutNode.isValidOwnerScope) {
             layoutNode.requestLookaheadRelayout()
         }
@@ -78,7 +78,7 @@
         block: () -> Unit
     ) {
         if (affectsLookahead && node.lookaheadRoot != null) {
-            observeReads(node, onCommitAffectingLookaheadLayout, block)
+            observeReads(node, onCommitAffectingLookahead, block)
         } else {
             observeReads(node, onCommitAffectingLayout, block)
         }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt
index d1d8cd0..6542fbf 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.platform
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocal
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.ExperimentalComposeUiApi
@@ -137,6 +138,14 @@
 val LocalTextInputService = staticCompositionLocalOf<TextInputService?> { null }
 
 /**
+ * The [CompositionLocal] to provide a [SoftwareKeyboardController] that can control the current
+ * software keyboard.
+ *
+ * Will be null if the software keyboard cannot be controlled.
+ */
+val LocalSoftwareKeyboardController = staticCompositionLocalOf<SoftwareKeyboardController?> { null }
+
+/**
  * The CompositionLocal to provide platform text input services.
  *
  * This is a low-level API for code that talks directly to the platform input method framework.
@@ -201,6 +210,7 @@
         LocalInputModeManager provides owner.inputModeManager,
         LocalLayoutDirection provides owner.layoutDirection,
         LocalTextInputService provides owner.textInputService,
+        LocalSoftwareKeyboardController provides owner.softwareKeyboardController,
         LocalPlatformTextInputPluginRegistry provides owner.platformTextInputPluginRegistry,
         LocalTextToolbar provides owner.textToolbar,
         LocalUriHandler provides uriHandler,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/LocalSoftwareKeyboardController.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/LocalSoftwareKeyboardController.kt
deleted file mode 100644
index dbcc400..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/LocalSoftwareKeyboardController.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2021 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.compose.ui.platform
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ProvidedValue
-import androidx.compose.runtime.compositionLocalOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.text.input.TextInputService
-
-@ExperimentalComposeUiApi
-public object LocalSoftwareKeyboardController {
-
-    private val LocalSoftwareKeyboardController =
-        compositionLocalOf<SoftwareKeyboardController?> { null }
-
-    /**
-     * Return a [SoftwareKeyboardController] that can control the current software keyboard.
-     *
-     * If it is not provided, the default implementation will delegate to [LocalTextInputService].
-     *
-     * Returns null if the software keyboard cannot be controlled.
-     */
-    @ExperimentalComposeUiApi
-    public val current: SoftwareKeyboardController?
-        @Composable get() {
-            return LocalSoftwareKeyboardController.current ?: delegatingController()
-        }
-
-    @Composable
-    private fun delegatingController(): SoftwareKeyboardController? {
-        val textInputService = LocalTextInputService.current ?: return null
-        return remember(textInputService) {
-            DelegatingSoftwareKeyboardController(textInputService)
-        }
-    }
-
-    /**
-     * Set the key [LocalSoftwareKeyboardController] in [CompositionLocalProvider].
-     */
-    public infix fun provides(
-        softwareKeyboardController: SoftwareKeyboardController
-    ): ProvidedValue<SoftwareKeyboardController?> {
-        return LocalSoftwareKeyboardController.provides(softwareKeyboardController)
-    }
-}
-
-@ExperimentalComposeUiApi
-private class DelegatingSoftwareKeyboardController(
-    val textInputService: TextInputService
-) : SoftwareKeyboardController {
-    override fun show() {
-        @Suppress("DEPRECATION")
-        textInputService.showSoftwareKeyboard()
-    }
-
-    override fun hide() {
-        @Suppress("DEPRECATION")
-        textInputService.hideSoftwareKeyboard()
-    }
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/SoftwareKeyboardController.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/SoftwareKeyboardController.kt
index 9072419..12a4c1a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/SoftwareKeyboardController.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/SoftwareKeyboardController.kt
@@ -17,12 +17,11 @@
 package androidx.compose.ui.platform
 
 import androidx.compose.runtime.Stable
-import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.text.input.TextInputService
 
 /**
  * Provide software keyboard control.
  */
-@ExperimentalComposeUiApi
 @Stable
 interface SoftwareKeyboardController {
     /**
@@ -48,15 +47,6 @@
     fun show()
 
     /**
-     * @see show
-     */
-    @Deprecated(
-        "Use show instead.",
-        ReplaceWith("show()")
-    )
-    fun showSoftwareKeyboard() = show()
-
-    /**
      * Hide the software keyboard.
      *
      * This request is best effort, if the system cannot hide the software keyboard this call
@@ -68,13 +58,18 @@
      * recomposition.
      */
     fun hide()
+}
 
-    /**
-     * @see hide
-     */
-    @Deprecated(
-        "Use hide instead.",
-        ReplaceWith("hide()")
-    )
-    fun hideSoftwareKeyboard() = hide()
+internal class DelegatingSoftwareKeyboardController(
+    val textInputService: TextInputService
+) : SoftwareKeyboardController {
+    override fun show() {
+        @Suppress("DEPRECATION")
+        textInputService.showSoftwareKeyboard()
+    }
+
+    override fun hide() {
+        @Suppress("DEPRECATION")
+        textInputService.hideSoftwareKeyboard()
+    }
 }
\ No newline at end of file
diff --git a/coordinatorlayout/OWNERS b/coordinatorlayout/OWNERS
index 347e71c..60785d2 100644
--- a/coordinatorlayout/OWNERS
+++ b/coordinatorlayout/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 461380
 aelias@google.com
 ryanmentley@google.com
diff --git a/core/core-ktx/OWNERS b/core/core-ktx/OWNERS
index 6fd8227..b8e9338 100644
--- a/core/core-ktx/OWNERS
+++ b/core/core-ktx/OWNERS
@@ -1 +1,2 @@
+# Bug component: 461355
 yboyar@google.com
diff --git a/core/core-location-altitude-proto/OWNERS b/core/core-location-altitude-proto/OWNERS
index b95c469..b2b8f3b 100644
--- a/core/core-location-altitude-proto/OWNERS
+++ b/core/core-location-altitude-proto/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 461355
 bjj@google.com
 sooniln@google.com
diff --git a/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/db/package-info.java b/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/db/package-info.java
index fa7d778..2d75044 100644
--- a/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/db/package-info.java
+++ b/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/db/package-info.java
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-/** @hide */
 @RestrictTo(LIBRARY)
 package androidx.core.location.altitude.impl.db;
 
diff --git a/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/package-info.java b/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/package-info.java
index c61153e..378469d 100644
--- a/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/package-info.java
+++ b/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/package-info.java
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-/** @hide */
 @RestrictTo(LIBRARY)
 package androidx.core.location.altitude.impl;
 
diff --git a/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/proto/package-info.java b/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/proto/package-info.java
index 81aad0e..6b2f774 100644
--- a/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/proto/package-info.java
+++ b/core/core-location-altitude/src/main/java/androidx/core/location/altitude/impl/proto/package-info.java
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-/** @hide */
 @RestrictTo(LIBRARY)
 package androidx.core.location.altitude.impl.proto;
 
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/utils/TestUtils.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/utils/TestUtils.kt
index a15a775..fc1e41d 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/utils/TestUtils.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/utils/TestUtils.kt
@@ -48,7 +48,7 @@
     const val TEST_CALL_ATTRIB_NAME = "Elon Musk"
     const val OUTGOING_NAME = "Larry Page"
     const val INCOMING_NAME = "Sundar Pichai"
-    const val WAIT_ON_ASSERTS_TO_FINISH_TIMEOUT = 5000L
+    const val WAIT_ON_ASSERTS_TO_FINISH_TIMEOUT = 10000L
     const val WAIT_ON_CALL_STATE_TIMEOUT = 8000L
     const val WAIT_ON_IN_CALL_SERVICE_CALL_COUNT_TIMEOUT = 5000L
     const val ALL_CALL_CAPABILITIES = (CallAttributesCompat.SUPPORTS_SET_INACTIVE
diff --git a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
index 55fa032..1716ef1 100644
--- a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
@@ -2453,6 +2453,38 @@
         assertFalse(extras.getBoolean(NotificationCompat.EXTRA_CALL_IS_VIDEO));
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 24) // TODO: Should be 20. Adjust when b/288084025 is fixed.
+    public void testCallStyle_preservesCustomActions() {
+        PendingIntent hangupIntent = createIntent("hangup");
+        Person person = new Person.Builder().setName("test name").build();
+        NotificationCompat.CallStyle callStyle = NotificationCompat.CallStyle.forOngoingCall(
+                person, hangupIntent);
+        NotificationCompat.Action customAction = new NotificationCompat.Action.Builder(
+                IconCompat.createWithResource(mContext, R.drawable.notification_bg),
+                "Custom!", null).build();
+
+        Notification notification = new NotificationCompat.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .addAction(customAction)
+                .setStyle(callStyle)
+                .build();
+
+        Notification.Action[] resultActions = notification.actions;
+        assertThat(resultActions).hasLength(2); // Hang up + custom (fits on all Android versions).
+        // But ordering is different per version.
+        if (Build.VERSION.SDK_INT <= 30 || Build.VERSION.SDK_INT >= 34) {
+            assertThat(resultActions[0].title.toString()).isEqualTo(
+                    mContext.getString(R.string.call_notification_hang_up_action));
+            assertThat(resultActions[1].title.toString()).isEqualTo(customAction.title.toString());
+        } else {
+            assertThat(resultActions[0].title.toString()).isEqualTo(customAction.title.toString());
+            assertThat(resultActions[1].title.toString()).isEqualTo(
+                    mContext.getString(R.string.call_notification_hang_up_action));
+        }
+    }
+
     @SdkSuppress(minSdkVersion = 20)
     @Test
     public void testCallStyle_getActionsListWithSystemAndContextualActionsForIncoming() {
diff --git a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
index 64623b1..971a915 100644
--- a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
@@ -16,6 +16,8 @@
 
 package androidx.core.view;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -59,6 +61,8 @@
 import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.google.common.truth.Truth;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -579,6 +583,28 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void testSetAccessibilityDelegate_viewAutoImportant_makesViewImportant() {
+        ViewCompat.setImportantForAccessibility(mView, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+        assertThat(mView.getImportantForAccessibility()).isEqualTo(
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+        ViewCompat.setAccessibilityDelegate(mView, new AccessibilityDelegateCompat());
+        assertThat(mView.getImportantForAccessibility()).isEqualTo(
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+    }
+
+    @SdkSuppress(minSdkVersion = 21)
+    @Test
+    public void testSetAccessibilityDelegate_viewUnimportant_doesNotMakeViewImportant() {
+        ViewCompat.setImportantForAccessibility(mView, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
+        Truth.assertThat(mView.getImportantForAccessibility()).isEqualTo(
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
+        ViewCompat.setAccessibilityDelegate(mView, new AccessibilityDelegateCompat());
+        Truth.assertThat(mView.getImportantForAccessibility()).isEqualTo(
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
+    }
+
+    @Test
     public void testAccessibilityDelegateStillWorksAfterCompatImplicitlyAdded() {
         View.AccessibilityDelegate mockDelegate = mock(View.AccessibilityDelegate.class);
         mView.setAccessibilityDelegate(mockDelegate);
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompat.java b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
index 36de5f08..e8bab6f 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompat.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
@@ -5004,9 +5004,18 @@
                         }
                 }
                 if (style != null) {
-                    // Before applying the style, we clear the actions.
+                    // Before applying the style, we clear any previous style-provided actions.
+                    ArrayList<Action> customActions = new ArrayList<>();
+                    for (Action action : mBuilder.mActions) {
+                        if (!isActionAddedByCallStyle(action)) {
+                            customActions.add(action);
+                        }
+                    }
                     Api24Impl.clearActions(builderAccessor.getBuilder());
-
+                    for (Action action : customActions) {
+                        Api20Impl.addAction(builderAccessor.getBuilder(),
+                                getActionFromActionCompat(action));
+                    }
                     Api16Impl.setBuilder(style, builderAccessor.getBuilder());
                     if (mAnswerButtonColor != null) {
                         Api31Impl.setAnswerButtonColorHint(style, mAnswerButtonColor);
@@ -6196,6 +6205,9 @@
                 if (Build.VERSION.SDK_INT >= 31) {
                     builder.setAuthenticationRequired(Api31Impl.isAuthenticationRequired(action));
                 }
+                if (Build.VERSION.SDK_INT >= 20) {
+                    builder.addExtras(Api20Impl.getExtras(action));
+                }
                 return builder;
             }
 
@@ -6433,6 +6445,10 @@
                     return action.getRemoteInputs();
                 }
 
+                @DoNotInline
+                static Bundle getExtras(Notification.Action action) {
+                    return action.getExtras();
+                }
             }
 
             /**
diff --git a/core/core/src/main/java/androidx/core/view/ViewCompat.java b/core/core/src/main/java/androidx/core/view/ViewCompat.java
index 5f551cf..9ca0a09 100644
--- a/core/core/src/main/java/androidx/core/view/ViewCompat.java
+++ b/core/core/src/main/java/androidx/core/view/ViewCompat.java
@@ -767,6 +767,7 @@
                 && (getAccessibilityDelegateInternal(v) instanceof AccessibilityDelegateAdapter)) {
             delegate = new AccessibilityDelegateCompat();
         }
+        setImportantForAccessibilityIfNeeded(v);
         v.setAccessibilityDelegate(delegate == null ? null : delegate.getBridge());
     }
 
@@ -4821,7 +4822,7 @@
             Api19Impl.setContentChangeTypes(event, changeType);
             if (isVisibleAccessibilityPane) {
                 event.getText().add(getAccessibilityPaneTitle(view));
-                setViewImportanceForAccessibilityIfNeeded(view);
+                setImportantForAccessibilityIfNeeded(view);
             }
             view.sendAccessibilityEventUnchecked(event);
         } else if (changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) {
@@ -4844,23 +4845,12 @@
         }
     }
 
-    private static void setViewImportanceForAccessibilityIfNeeded(View view) {
+    private static void setImportantForAccessibilityIfNeeded(View view) {
         if (ViewCompat.getImportantForAccessibility(view)
                 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
             ViewCompat.setImportantForAccessibility(view,
                     ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
-        // Check parent mode to ensure we're not hidden.
-        ViewParent parent = view.getParent();
-        while (parent instanceof View) {
-            if (ViewCompat.getImportantForAccessibility((View) parent)
-                    == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
-                ViewCompat.setImportantForAccessibility(view,
-                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
-                break;
-            }
-            parent = parent.getParent();
-        }
     }
 
     private static final AccessibilityPaneVisibilityManager sAccessibilityPaneVisibilityManager =
diff --git a/credentials/credentials-play-services-auth/OWNERS b/credentials/credentials-play-services-auth/OWNERS
index 7b0e47f..67ec32c 100644
--- a/credentials/credentials-play-services-auth/OWNERS
+++ b/credentials/credentials-play-services-auth/OWNERS
@@ -1,4 +1,4 @@
-# Bug Component: 1218609
+# Bug component: 1218609
 sgjerry@google.com
 helenqin@google.com
 reemabajwa@google.com
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
index 45af09f..e4cddda 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
@@ -45,68 +45,66 @@
     public void convertRequestToPlayServices_setPasswordOptionRequestAndFalseAutoSelect_success() {
         ActivityScenario<TestCredentialsActivity> activityScenario =
                 ActivityScenario.launch(TestCredentialsActivity.class);
-        activityScenario.onActivity(activity -> {
+        activityScenario.onActivity(
+                activity -> {
+                    BeginSignInRequest actualResponse =
+                            CredentialProviderBeginSignInController.getInstance(activity)
+                                    .convertRequestToPlayServices(
+                                            new GetCredentialRequest(
+                                                    List.of(new GetPasswordOption())));
 
-            BeginSignInRequest actualResponse =
-                    CredentialProviderBeginSignInController
-                            .getInstance(activity)
-                            .convertRequestToPlayServices(new GetCredentialRequest(List.of(
-                                    new GetPasswordOption()
-                            )));
-
-            assertThat(actualResponse.getPasswordRequestOptions().isSupported()).isTrue();
-            assertThat(actualResponse.isAutoSelectEnabled()).isFalse();
-        });
+                    assertThat(actualResponse.getPasswordRequestOptions().isSupported()).isTrue();
+                    assertThat(actualResponse.isAutoSelectEnabled()).isFalse();
+                });
     }
 
     @Test
     public void convertRequestToPlayServices_setPasswordOptionRequestAndTrueAutoSelect_success() {
         ActivityScenario<TestCredentialsActivity> activityScenario =
                 ActivityScenario.launch(TestCredentialsActivity.class);
-        activityScenario.onActivity(activity -> {
+        activityScenario.onActivity(
+                activity -> {
+                    BeginSignInRequest actualResponse =
+                            CredentialProviderBeginSignInController.getInstance(activity)
+                                    .convertRequestToPlayServices(
+                                            new GetCredentialRequest(
+                                                    List.of(
+                                                            new GetPasswordOption(
+                                                                    new HashSet<>(), true))));
 
-            BeginSignInRequest actualResponse =
-                    CredentialProviderBeginSignInController
-                            .getInstance(activity)
-                            .convertRequestToPlayServices(new GetCredentialRequest(List.of(
-                                    new GetPasswordOption(new HashSet<>(), true)
-                            )));
-
-            assertThat(actualResponse.getPasswordRequestOptions().isSupported()).isTrue();
-            assertThat(actualResponse.isAutoSelectEnabled()).isTrue();
-        });
+                    assertThat(actualResponse.getPasswordRequestOptions().isSupported()).isTrue();
+                    assertThat(actualResponse.isAutoSelectEnabled()).isTrue();
+                });
     }
 
     @Test
     public void convertRequestToPlayServices_nullRequest_throws() {
         ActivityScenario<TestCredentialsActivity> activityScenario =
                 ActivityScenario.launch(TestCredentialsActivity.class);
-        activityScenario.onActivity(activity -> {
-
-            assertThrows(
-                    "null get credential request must throw exception",
-                    NullPointerException.class,
-                    () -> CredentialProviderBeginSignInController
-                            .getInstance(activity)
-                            .convertRequestToPlayServices(null)
-            );
-        });
+        activityScenario.onActivity(
+                activity -> {
+                    assertThrows(
+                            "null get credential request must throw exception",
+                            NullPointerException.class,
+                            () ->
+                                    CredentialProviderBeginSignInController.getInstance(activity)
+                                            .convertRequestToPlayServices(null));
+                });
     }
 
     @Test
     public void convertResponseToCredentialManager_nullRequest_throws() {
         ActivityScenario<TestCredentialsActivity> activityScenario =
                 ActivityScenario.launch(TestCredentialsActivity.class);
-        activityScenario.onActivity(activity -> {
-
-            assertThrows(
-                    "null sign in credential response must throw exception",
-                    NullPointerException.class,
-                    () -> CredentialProviderBeginSignInController
-                            .getInstance(activity)
-                            .convertResponseToCredentialManager(null)
-            );
-        });
+        activityScenario.onActivity(
+                activity -> {
+                    assertThrows(
+                            "null sign in credential response must throw exception",
+                            NullPointerException.class,
+                            () ->
+                                    CredentialProviderBeginSignInController.getInstance(activity)
+                                            .convertResponseToCredentialManager(null));
+                });
     }
 
     @Test
@@ -114,39 +112,54 @@
         ActivityScenario<TestCredentialsActivity> activityScenario =
                 ActivityScenario.launch(TestCredentialsActivity.class);
 
-        GetGoogleIdOption option = new GetGoogleIdOption.Builder()
-                .setServerClientId("server_client_id")
-                .setNonce("nonce")
-                .setFilterByAuthorizedAccounts(true)
-                .setRequestVerifiedPhoneNumber(false)
-                .associateLinkedAccounts("link_service_id", List.of("a", "b", "c"))
-                .setAutoSelectEnabled(true)
-                .build();
+        GetGoogleIdOption option =
+                new GetGoogleIdOption.Builder()
+                        .setServerClientId("server_client_id")
+                        .setNonce("nonce")
+                        .setFilterByAuthorizedAccounts(true)
+                        .setRequestVerifiedPhoneNumber(false)
+                        .associateLinkedAccounts("link_service_id", List.of("a", "b", "c"))
+                        .setAutoSelectEnabled(true)
+                        .build();
 
-        activityScenario.onActivity(activity -> {
+        activityScenario.onActivity(
+                activity -> {
+                    BeginSignInRequest actualRequest =
+                            CredentialProviderBeginSignInController.getInstance(activity)
+                                    .convertRequestToPlayServices(
+                                            new GetCredentialRequest(List.of(option)));
 
-            BeginSignInRequest actualRequest =
-                    CredentialProviderBeginSignInController
-                            .getInstance(activity)
-                            .convertRequestToPlayServices(new GetCredentialRequest(List.of(
-                                    option
-                            )));
+                    assertThat(actualRequest.getGoogleIdTokenRequestOptions().isSupported())
+                            .isTrue();
+                    assertThat(actualRequest.isAutoSelectEnabled()).isTrue();
 
-            assertThat(actualRequest.getGoogleIdTokenRequestOptions().isSupported()).isTrue();
-            assertThat(actualRequest.isAutoSelectEnabled()).isTrue();
+                    BeginSignInRequest.GoogleIdTokenRequestOptions actualOption =
+                            actualRequest.getGoogleIdTokenRequestOptions();
+                    assertThat(actualOption.getServerClientId())
+                            .isEqualTo(option.getServerClientId());
+                    assertThat(actualOption.getNonce()).isEqualTo(option.getNonce());
+                    assertThat(actualOption.filterByAuthorizedAccounts())
+                            .isEqualTo(option.getFilterByAuthorizedAccounts());
+                    assertThat(actualOption.requestVerifiedPhoneNumber())
+                            .isEqualTo(option.getRequestVerifiedPhoneNumber());
+                    assertThat(actualOption.getLinkedServiceId())
+                            .isEqualTo(option.getLinkedServiceId());
+                    assertThat(actualOption.getIdTokenDepositionScopes())
+                            .isEqualTo(option.getIdTokenDepositionScopes());
+                });
+    }
 
-            BeginSignInRequest.GoogleIdTokenRequestOptions actualOption =
-                    actualRequest.getGoogleIdTokenRequestOptions();
-            assertThat(actualOption.getServerClientId()).isEqualTo(option.getServerClientId());
-            assertThat(actualOption.getNonce()).isEqualTo(option.getNonce());
-            assertThat(actualOption.filterByAuthorizedAccounts()).isEqualTo(
-                    option.getFilterByAuthorizedAccounts());
-            assertThat(actualOption.requestVerifiedPhoneNumber()).isEqualTo(
-                    option.getRequestVerifiedPhoneNumber());
-            assertThat(actualOption.getLinkedServiceId()).isEqualTo(option.getLinkedServiceId());
-            assertThat(actualOption.getIdTokenDepositionScopes()).isEqualTo(
-                    option.getIdTokenDepositionScopes());
-
-        });
+    @Test
+    public void duplicateGetInstance_shouldBeEqual() {
+        ActivityScenario<TestCredentialsActivity> activityScenario =
+                ActivityScenario.launch(TestCredentialsActivity.class);
+        activityScenario.onActivity(
+                activity -> {
+                    CredentialProviderBeginSignInController firstInstance =
+                            CredentialProviderBeginSignInController.getInstance(activity);
+                    CredentialProviderBeginSignInController secondInstance =
+                            CredentialProviderBeginSignInController.getInstance(activity);
+                    assertThat(firstInstance).isEqualTo(secondInstance);
+                });
     }
 }
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
index e579c2b..98ba9e3 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
@@ -117,4 +117,17 @@
                 .isEqualTo(option.idTokenDepositionScopes)
         }
     }
+
+    @Test
+    fun duplicateGetInstance_shouldBeEqual() {
+        val activityScenario = ActivityScenario.launch(
+            TestCredentialsActivity::class.java
+        )
+        activityScenario.onActivity { activity: TestCredentialsActivity? ->
+
+            val firstInstance = getInstance(activity!!)
+            val secondInstance = getInstance(activity)
+            assertThat(firstInstance).isEqualTo(secondInstance)
+        }
+    }
 }
\ No newline at end of file
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java
index 7e75793..00b86ba 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerJavaTest.java
@@ -34,11 +34,11 @@
 
 import com.google.android.gms.auth.api.identity.SignInPassword;
 
+import kotlin.Unit;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import kotlin.Unit;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CredentialProviderCreatePasswordControllerJavaTest {
@@ -48,16 +48,16 @@
         ActivityScenario<TestCredentialsActivity> activityScenario =
                 ActivityScenario.launch(TestCredentialsActivity.class);
         String expectedResponseType = new CreatePasswordResponse().getType();
-        activityScenario.onActivity(activity -> {
+        activityScenario.onActivity(
+                activity -> {
+                    CreateCredentialResponse actualResponse =
+                            CredentialProviderCreatePasswordController.getInstance(activity)
+                                    .convertResponseToCredentialManager(Unit.INSTANCE);
 
-            CreateCredentialResponse actualResponse =
-                    CredentialProviderCreatePasswordController
-                            .getInstance(activity)
-                            .convertResponseToCredentialManager(Unit.INSTANCE);
-
-            assertThat(actualResponse.getType()).isEqualTo(expectedResponseType);
-            assertThat(TestUtils.Companion.equals(actualResponse.getData(), Bundle.EMPTY)).isTrue();
-        });
+                    assertThat(actualResponse.getType()).isEqualTo(expectedResponseType);
+                    assertThat(TestUtils.Companion.equals(actualResponse.getData(), Bundle.EMPTY))
+                            .isTrue();
+                });
     }
 
     @Test
@@ -66,52 +66,61 @@
                 ActivityScenario.launch(TestCredentialsActivity.class);
         String expectedId = "LM";
         String expectedPassword = "SodaButton";
-        activityScenario.onActivity(activity -> {
+        activityScenario.onActivity(
+                activity -> {
+                    SignInPassword actualRequest =
+                            CredentialProviderCreatePasswordController.getInstance(activity)
+                                    .convertRequestToPlayServices(
+                                            new CreatePasswordRequest(expectedId, expectedPassword))
+                                    .getSignInPassword();
 
-            SignInPassword actualRequest =
-                    CredentialProviderCreatePasswordController
-                            .getInstance(activity)
-                            .convertRequestToPlayServices(new CreatePasswordRequest(
-                                    expectedId, expectedPassword
-                            ))
-                            .getSignInPassword();
-
-            assertThat(actualRequest.getPassword()).isEqualTo(expectedPassword);
-            assertThat(actualRequest.getId()).isEqualTo(expectedId);
-        });
+                    assertThat(actualRequest.getPassword()).isEqualTo(expectedPassword);
+                    assertThat(actualRequest.getId()).isEqualTo(expectedId);
+                });
     }
 
     @Test
     public void convertRequestToPlayServices_nullRequest_throws() {
         ActivityScenario<TestCredentialsActivity> activityScenario =
                 ActivityScenario.launch(TestCredentialsActivity.class);
-        activityScenario.onActivity(activity -> {
-
-            assertThrows(
-                    "null create password request must throw exception",
-                    NullPointerException.class,
-                    () -> CredentialProviderCreatePasswordController
-                            .getInstance(activity)
-                            .convertRequestToPlayServices(null)
-                            .getSignInPassword()
-            );
-        });
+        activityScenario.onActivity(
+                activity -> {
+                    assertThrows(
+                            "null create password request must throw exception",
+                            NullPointerException.class,
+                            () ->
+                                    CredentialProviderCreatePasswordController.getInstance(activity)
+                                            .convertRequestToPlayServices(null)
+                                            .getSignInPassword());
+                });
     }
 
     @Test
     public void convertResponseToCredentialManager_nullRequest_throws() {
         ActivityScenario<TestCredentialsActivity> activityScenario =
                 ActivityScenario.launch(TestCredentialsActivity.class);
-        activityScenario.onActivity(activity -> {
-
-            assertThrows(
-                    "null unit response must throw exception",
-                    NullPointerException.class,
-                    () -> CredentialProviderCreatePasswordController
-                            .getInstance(activity)
-                            .convertResponseToCredentialManager(null)
-            );
-        });
+        activityScenario.onActivity(
+                activity -> {
+                    assertThrows(
+                            "null unit response must throw exception",
+                            NullPointerException.class,
+                            () ->
+                                    CredentialProviderCreatePasswordController.getInstance(activity)
+                                            .convertResponseToCredentialManager(null));
+                });
     }
 
+    @Test
+    public void duplicateGetInstance_shouldBeEqual() {
+        ActivityScenario<TestCredentialsActivity> activityScenario =
+                ActivityScenario.launch(TestCredentialsActivity.class);
+        activityScenario.onActivity(
+                activity -> {
+                    CredentialProviderCreatePasswordController firstInstance =
+                            CredentialProviderCreatePasswordController.getInstance(activity);
+                    CredentialProviderCreatePasswordController secondInstance =
+                            CredentialProviderCreatePasswordController.getInstance(activity);
+                    assertThat(firstInstance).isEqualTo(secondInstance);
+                });
+    }
 }
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt
index e499bce..128c970 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpassword/CredentialProviderCreatePasswordControllerTest.kt
@@ -67,4 +67,17 @@
             assertThat(actualRequest.id).isEqualTo(expectedId)
         }
     }
+
+    @Test
+    fun duplicateGetInstance_shouldBeEqual() {
+        val activityScenario = ActivityScenario.launch(
+            TestCredentialsActivity::class.java
+        )
+        activityScenario.onActivity { activity: TestCredentialsActivity? ->
+
+            val firstInstance = getInstance(activity!!)
+            val secondInstance = getInstance(activity)
+            assertThat(firstInstance).isEqualTo(secondInstance)
+        }
+    }
 }
\ No newline at end of file
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java
index 3f8e78e..8fba8ff 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CredentialProviderCreatePublicKeyCredentialControllerJavaTest.java
@@ -29,6 +29,7 @@
 import static androidx.credentials.playservices.createkeycredential.CreatePublicKeyCredentialControllerTestUtils.createJsonObjectFromPublicKeyCredentialCreationOptions;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
 
@@ -109,14 +110,19 @@
         ActivityScenario<TestCredentialsActivity> activityScenario =
                 ActivityScenario.launch(TestCredentialsActivity.class);
         activityScenario.onActivity(activity -> {
-            // TODO("Add a check for keywords in error messages")
-            assertThrows("Expected bad required json to throw",
-                    JSONException.class,
-                    () -> CredentialProviderCreatePublicKeyCredentialController
+            try {
+                CredentialProviderCreatePublicKeyCredentialController
                             .getInstance(activity)
                             .convertRequestToPlayServices(
                                     new CreatePublicKeyCredentialRequest(
-                                            MAIN_CREATE_JSON_MISSING_REQUIRED_FIELD)));
+                                            MAIN_CREATE_JSON_MISSING_REQUIRED_FIELD));
+
+                // Should not reach here.
+                assertWithMessage("Exception should be thrown").that(true).isFalse();
+            } catch (Exception e) {
+                assertThat(e.getMessage().contains("No value for id")).isTrue();
+                assertThat(e.getClass().getName().contains("JSONException")).isTrue();
+            }
         });
     }
 
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
index 2789a83..93d656c 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
@@ -238,7 +238,6 @@
     companion object {
         private const val TAG = "BeginSignIn"
         private var controller: CredentialProviderBeginSignInController? = null
-        // TODO(b/262924507) : Test multiple calls (re-instantiation validates but just in case)
 
         /**
          * This finds a past version of the [CredentialProviderBeginSignInController] if it exists,
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
index 15222d4..865d523 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
@@ -136,7 +136,6 @@
     companion object {
         private const val TAG = "CreatePassword"
         private var controller: CredentialProviderCreatePasswordController? = null
-        // TODO(b/262924507) : Test multiple calls (re-instantiation validates but just in case)
         /**
          * This finds a past version of the
          * [CredentialProviderCreatePasswordController] if it exists, otherwise
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
index 4ef140b..07d63b1 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
@@ -403,7 +403,6 @@
                         UserVerificationMethodExtension(true)
                     )
                 }
-                // TODO("Ensure JSON keys are correctly named")
                 builder.setAuthenticationExtensions(extensionBuilder.build())
             }
         }
diff --git a/credentials/credentials/api/current.txt b/credentials/credentials/api/current.txt
index c26fd59..3a1d7df 100644
--- a/credentials/credentials/api/current.txt
+++ b/credentials/credentials/api/current.txt
@@ -221,36 +221,33 @@
   }
 
   @RequiresApi(34) public final class PrepareGetCredentialResponse {
-    method public kotlin.jvm.functions.Function1<java.lang.String,java.lang.Boolean>? getCredentialTypeHandler();
-    method public kotlin.jvm.functions.Function0<java.lang.Boolean>? getHasAuthenticationResultsHandler();
-    method public kotlin.jvm.functions.Function0<java.lang.Boolean>? getHasRemoteResultsHandler();
+    method public kotlin.jvm.functions.Function1<java.lang.String,java.lang.Boolean>? getCredentialTypeDelegate();
+    method public kotlin.jvm.functions.Function0<java.lang.Boolean>? getHasAuthResultsDelegate();
+    method public kotlin.jvm.functions.Function0<java.lang.Boolean>? getHasRemoteResultsDelegate();
     method public androidx.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle? getPendingGetCredentialHandle();
     method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasAuthenticationResults();
     method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasCredentialResults(String credentialType);
     method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasRemoteResults();
     method public boolean isNullHandlesForTest();
-    property public final kotlin.jvm.functions.Function1<java.lang.String,java.lang.Boolean>? credentialTypeHandler;
-    property public final kotlin.jvm.functions.Function0<java.lang.Boolean>? hasAuthenticationResultsHandler;
-    property public final kotlin.jvm.functions.Function0<java.lang.Boolean>? hasRemoteResultsHandler;
+    property public final kotlin.jvm.functions.Function1<java.lang.String,java.lang.Boolean>? credentialTypeDelegate;
+    property public final kotlin.jvm.functions.Function0<java.lang.Boolean>? hasAuthResultsDelegate;
+    property public final kotlin.jvm.functions.Function0<java.lang.Boolean>? hasRemoteResultsDelegate;
     property public final boolean isNullHandlesForTest;
     property public final androidx.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle? pendingGetCredentialHandle;
   }
 
-  public static final class PrepareGetCredentialResponse.Builder {
-    ctor public PrepareGetCredentialResponse.Builder();
-    method public androidx.credentials.PrepareGetCredentialResponse build();
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setCredentialTypeHandler(kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> handler);
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setFrameworkResponse(android.credentials.PrepareGetCredentialResponse? resp);
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setHasAuthenticationResultsHandler(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setHasRemoteResultsHandler(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
-    method @VisibleForTesting public androidx.credentials.PrepareGetCredentialResponse.Builder setIsNullHandlesForTest(boolean setValue);
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setPendingGetCredentialHandle(androidx.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle handle);
-  }
-
   @RequiresApi(34) public static final class PrepareGetCredentialResponse.PendingGetCredentialHandle {
     ctor public PrepareGetCredentialResponse.PendingGetCredentialHandle(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle? frameworkHandle);
   }
 
+  @VisibleForTesting public static final class PrepareGetCredentialResponse.TestBuilder {
+    ctor public PrepareGetCredentialResponse.TestBuilder();
+    method public androidx.credentials.PrepareGetCredentialResponse build();
+    method public androidx.credentials.PrepareGetCredentialResponse.TestBuilder setCredentialTypeDelegate(kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> handler);
+    method public androidx.credentials.PrepareGetCredentialResponse.TestBuilder setHasAuthResultsDelegate(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
+    method public androidx.credentials.PrepareGetCredentialResponse.TestBuilder setHasRemoteResultsDelegate(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
+  }
+
   public final class PublicKeyCredential extends androidx.credentials.Credential {
     ctor public PublicKeyCredential(String authenticationResponseJson);
     method public String getAuthenticationResponseJson();
@@ -704,7 +701,7 @@
     property public final android.content.pm.SigningInfo signingInfo;
   }
 
-  @RequiresApi(28) public final class CreateEntry {
+  @RequiresApi(26) public final class CreateEntry {
     ctor public CreateEntry(CharSequence accountName, android.app.PendingIntent pendingIntent, optional CharSequence? description, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon? icon, optional Integer? passwordCredentialCount, optional Integer? publicKeyCredentialCount, optional Integer? totalCredentialCount, optional boolean isAutoSelectAllowed);
     method public CharSequence getAccountName();
     method public CharSequence? getDescription();
@@ -750,7 +747,7 @@
     method public abstract void onClearCredentialStateRequest(androidx.credentials.provider.ProviderClearCredentialStateRequest request, android.os.CancellationSignal cancellationSignal, android.os.OutcomeReceiver<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
   }
 
-  @RequiresApi(28) public final class CustomCredentialEntry extends androidx.credentials.provider.CredentialEntry {
+  @RequiresApi(26) public final class CustomCredentialEntry extends androidx.credentials.provider.CredentialEntry {
     ctor public CustomCredentialEntry(android.content.Context context, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption, optional CharSequence? subtitle, optional CharSequence? typeDisplayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
     method public android.graphics.drawable.Icon getIcon();
     method public java.time.Instant? getLastUsedTime();
@@ -788,7 +785,7 @@
     method @RequiresApi(34) public static android.credentials.GetCredentialResponse? getGetCredentialResponse(android.content.Intent);
   }
 
-  @RequiresApi(28) public final class PasswordCredentialEntry extends androidx.credentials.provider.CredentialEntry {
+  @RequiresApi(26) public final class PasswordCredentialEntry extends androidx.credentials.provider.CredentialEntry {
     ctor public PasswordCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPasswordOption beginGetPasswordOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
     method public CharSequence? getDisplayName();
     method public android.graphics.drawable.Icon getIcon();
@@ -861,7 +858,7 @@
     property public final java.util.List<androidx.credentials.CredentialOption> credentialOptions;
   }
 
-  @RequiresApi(28) public final class PublicKeyCredentialEntry extends androidx.credentials.provider.CredentialEntry {
+  @RequiresApi(26) public final class PublicKeyCredentialEntry extends androidx.credentials.provider.CredentialEntry {
     ctor public PublicKeyCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
     method public CharSequence? getDisplayName();
     method public android.graphics.drawable.Icon getIcon();
diff --git a/credentials/credentials/api/restricted_current.txt b/credentials/credentials/api/restricted_current.txt
index c26fd59..3a1d7df 100644
--- a/credentials/credentials/api/restricted_current.txt
+++ b/credentials/credentials/api/restricted_current.txt
@@ -221,36 +221,33 @@
   }
 
   @RequiresApi(34) public final class PrepareGetCredentialResponse {
-    method public kotlin.jvm.functions.Function1<java.lang.String,java.lang.Boolean>? getCredentialTypeHandler();
-    method public kotlin.jvm.functions.Function0<java.lang.Boolean>? getHasAuthenticationResultsHandler();
-    method public kotlin.jvm.functions.Function0<java.lang.Boolean>? getHasRemoteResultsHandler();
+    method public kotlin.jvm.functions.Function1<java.lang.String,java.lang.Boolean>? getCredentialTypeDelegate();
+    method public kotlin.jvm.functions.Function0<java.lang.Boolean>? getHasAuthResultsDelegate();
+    method public kotlin.jvm.functions.Function0<java.lang.Boolean>? getHasRemoteResultsDelegate();
     method public androidx.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle? getPendingGetCredentialHandle();
     method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasAuthenticationResults();
     method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasCredentialResults(String credentialType);
     method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasRemoteResults();
     method public boolean isNullHandlesForTest();
-    property public final kotlin.jvm.functions.Function1<java.lang.String,java.lang.Boolean>? credentialTypeHandler;
-    property public final kotlin.jvm.functions.Function0<java.lang.Boolean>? hasAuthenticationResultsHandler;
-    property public final kotlin.jvm.functions.Function0<java.lang.Boolean>? hasRemoteResultsHandler;
+    property public final kotlin.jvm.functions.Function1<java.lang.String,java.lang.Boolean>? credentialTypeDelegate;
+    property public final kotlin.jvm.functions.Function0<java.lang.Boolean>? hasAuthResultsDelegate;
+    property public final kotlin.jvm.functions.Function0<java.lang.Boolean>? hasRemoteResultsDelegate;
     property public final boolean isNullHandlesForTest;
     property public final androidx.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle? pendingGetCredentialHandle;
   }
 
-  public static final class PrepareGetCredentialResponse.Builder {
-    ctor public PrepareGetCredentialResponse.Builder();
-    method public androidx.credentials.PrepareGetCredentialResponse build();
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setCredentialTypeHandler(kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> handler);
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setFrameworkResponse(android.credentials.PrepareGetCredentialResponse? resp);
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setHasAuthenticationResultsHandler(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setHasRemoteResultsHandler(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
-    method @VisibleForTesting public androidx.credentials.PrepareGetCredentialResponse.Builder setIsNullHandlesForTest(boolean setValue);
-    method public androidx.credentials.PrepareGetCredentialResponse.Builder setPendingGetCredentialHandle(androidx.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle handle);
-  }
-
   @RequiresApi(34) public static final class PrepareGetCredentialResponse.PendingGetCredentialHandle {
     ctor public PrepareGetCredentialResponse.PendingGetCredentialHandle(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle? frameworkHandle);
   }
 
+  @VisibleForTesting public static final class PrepareGetCredentialResponse.TestBuilder {
+    ctor public PrepareGetCredentialResponse.TestBuilder();
+    method public androidx.credentials.PrepareGetCredentialResponse build();
+    method public androidx.credentials.PrepareGetCredentialResponse.TestBuilder setCredentialTypeDelegate(kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> handler);
+    method public androidx.credentials.PrepareGetCredentialResponse.TestBuilder setHasAuthResultsDelegate(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
+    method public androidx.credentials.PrepareGetCredentialResponse.TestBuilder setHasRemoteResultsDelegate(kotlin.jvm.functions.Function0<java.lang.Boolean> handler);
+  }
+
   public final class PublicKeyCredential extends androidx.credentials.Credential {
     ctor public PublicKeyCredential(String authenticationResponseJson);
     method public String getAuthenticationResponseJson();
@@ -704,7 +701,7 @@
     property public final android.content.pm.SigningInfo signingInfo;
   }
 
-  @RequiresApi(28) public final class CreateEntry {
+  @RequiresApi(26) public final class CreateEntry {
     ctor public CreateEntry(CharSequence accountName, android.app.PendingIntent pendingIntent, optional CharSequence? description, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon? icon, optional Integer? passwordCredentialCount, optional Integer? publicKeyCredentialCount, optional Integer? totalCredentialCount, optional boolean isAutoSelectAllowed);
     method public CharSequence getAccountName();
     method public CharSequence? getDescription();
@@ -750,7 +747,7 @@
     method public abstract void onClearCredentialStateRequest(androidx.credentials.provider.ProviderClearCredentialStateRequest request, android.os.CancellationSignal cancellationSignal, android.os.OutcomeReceiver<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
   }
 
-  @RequiresApi(28) public final class CustomCredentialEntry extends androidx.credentials.provider.CredentialEntry {
+  @RequiresApi(26) public final class CustomCredentialEntry extends androidx.credentials.provider.CredentialEntry {
     ctor public CustomCredentialEntry(android.content.Context context, CharSequence title, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetCredentialOption beginGetCredentialOption, optional CharSequence? subtitle, optional CharSequence? typeDisplayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
     method public android.graphics.drawable.Icon getIcon();
     method public java.time.Instant? getLastUsedTime();
@@ -788,7 +785,7 @@
     method @RequiresApi(34) public static android.credentials.GetCredentialResponse? getGetCredentialResponse(android.content.Intent);
   }
 
-  @RequiresApi(28) public final class PasswordCredentialEntry extends androidx.credentials.provider.CredentialEntry {
+  @RequiresApi(26) public final class PasswordCredentialEntry extends androidx.credentials.provider.CredentialEntry {
     ctor public PasswordCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPasswordOption beginGetPasswordOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
     method public CharSequence? getDisplayName();
     method public android.graphics.drawable.Icon getIcon();
@@ -861,7 +858,7 @@
     property public final java.util.List<androidx.credentials.CredentialOption> credentialOptions;
   }
 
-  @RequiresApi(28) public final class PublicKeyCredentialEntry extends androidx.credentials.provider.CredentialEntry {
+  @RequiresApi(26) public final class PublicKeyCredentialEntry extends androidx.credentials.provider.CredentialEntry {
     ctor public PublicKeyCredentialEntry(android.content.Context context, CharSequence username, android.app.PendingIntent pendingIntent, androidx.credentials.provider.BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOption, optional CharSequence? displayName, optional java.time.Instant? lastUsedTime, optional android.graphics.drawable.Icon icon, optional boolean isAutoSelectAllowed);
     method public CharSequence? getDisplayName();
     method public android.graphics.drawable.Icon getIcon();
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/PrepareGetCredentialResponseJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/PrepareGetCredentialResponseJavaTest.java
index 51c0644..7bfc8b9 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/PrepareGetCredentialResponseJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/PrepareGetCredentialResponseJavaTest.java
@@ -53,9 +53,8 @@
         }
 
         // Construct the test class.
-        PrepareGetCredentialResponse response = new PrepareGetCredentialResponse.Builder()
-                .setIsNullHandlesForTest(true)
-                .setCredentialTypeHandler((val) ->
+        PrepareGetCredentialResponse response = new PrepareGetCredentialResponse.TestBuilder()
+                .setCredentialTypeDelegate((val) ->
                     val.equals("password") || val.equals("otherValid"))
                 .build();
 
@@ -74,9 +73,8 @@
         }
 
         // Construct the test class.
-        PrepareGetCredentialResponse response = new PrepareGetCredentialResponse.Builder()
-                .setIsNullHandlesForTest(true)
-                .setHasAuthenticationResultsHandler(() -> true)
+        PrepareGetCredentialResponse response = new PrepareGetCredentialResponse.TestBuilder()
+                .setHasAuthResultsDelegate(() -> true)
                 .build();
 
         // Verify the response.
@@ -94,9 +92,8 @@
         }
 
         // Construct the test class.
-        PrepareGetCredentialResponse response = new PrepareGetCredentialResponse.Builder()
-                .setIsNullHandlesForTest(true)
-                .setHasRemoteResultsHandler(() -> true)
+        PrepareGetCredentialResponse response = new PrepareGetCredentialResponse.TestBuilder()
+                .setHasRemoteResultsDelegate(() -> true)
                 .build();
 
         // Verify the response.
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/PrepareGetCredentialResponseTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/PrepareGetCredentialResponseTest.kt
index d75e64d..b15e06d 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/PrepareGetCredentialResponseTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/PrepareGetCredentialResponseTest.kt
@@ -53,9 +53,8 @@
         }
 
         // Construct the test class.
-        val response = PrepareGetCredentialResponse.Builder()
-            .setIsNullHandlesForTest(true)
-            .setCredentialTypeHandler { option ->
+        val response = PrepareGetCredentialResponse.TestBuilder()
+            .setCredentialTypeDelegate { option ->
                 option.equals("password") ||
                 option.equals("otherValid")
             }
@@ -76,9 +75,8 @@
         }
 
         // Construct the test class.
-        val response = PrepareGetCredentialResponse.Builder()
-            .setIsNullHandlesForTest(true)
-            .setHasAuthenticationResultsHandler {
+        val response = PrepareGetCredentialResponse.TestBuilder()
+            .setHasAuthResultsDelegate {
                 true
             }
             .build()
@@ -98,9 +96,8 @@
         }
 
         // Construct the test class.
-        val response = PrepareGetCredentialResponse.Builder()
-            .setIsNullHandlesForTest(true)
-            .setHasRemoteResultsHandler {
+        val response = PrepareGetCredentialResponse.TestBuilder()
+            .setHasRemoteResultsDelegate {
                 true
             }
             .build()
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/TestUtils.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/TestUtils.kt
index 4428cc5..200a073 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/TestUtils.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/TestUtils.kt
@@ -19,7 +19,6 @@
 import android.graphics.drawable.Icon
 import android.os.Build
 import android.os.Bundle
-import androidx.annotation.RequiresApi
 import androidx.core.os.BuildCompat
 import androidx.credentials.provider.CallingAppInfo
 
@@ -75,12 +74,13 @@
     return BuildCompat.isAtLeastU()
 }
 
-@RequiresApi(Build.VERSION_CODES.P)
 fun equals(a: Icon, b: Icon): Boolean {
+    if (Build.VERSION.SDK_INT <= 28) {
+        return true
+    }
     return a.type == b.type && a.resId == b.resId
 }
 
-@RequiresApi(34)
 fun equals(a: CallingAppInfo, b: CallingAppInfo): Boolean {
     return a.packageName == b.packageName && a.origin == b.origin
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCredentialResponseJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCredentialResponseJavaTest.java
index 5373fb5..a79d4ce 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCredentialResponseJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCredentialResponseJavaTest.java
@@ -24,7 +24,6 @@
 
 import static org.junit.Assert.assertThrows;
 
-import androidx.core.os.BuildCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
@@ -35,17 +34,13 @@
 import java.util.Arrays;
 import java.util.Collections;
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 26)
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BeginCreateCredentialResponseJavaTest {
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new BeginCreateCredentialResponse(
                 Arrays.asList(constructCreateEntryWithSimpleParams("AccountName",
                         "Desc")),
@@ -55,10 +50,6 @@
 
     @Test
     public void builder_createEntriesOnly_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new BeginCreateCredentialResponse.Builder().setCreateEntries(
                 Arrays.asList(constructCreateEntryWithSimpleParams("AccountName",
                         "Desc"))
@@ -67,10 +58,6 @@
 
     @Test
     public void builder_remoteEntryOnly_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new BeginCreateCredentialResponse.Builder().setRemoteEntry(
                 constructRemoteEntry()
         ).build();
@@ -78,10 +65,6 @@
 
     @Test
     public void constructor_nullList_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         assertThrows("Expected null list to throw NPE",
                 NullPointerException.class,
                 () -> new BeginCreateCredentialResponse(
@@ -91,10 +74,6 @@
 
     @Test
     public void buildConstruct_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new BeginCreateCredentialResponse.Builder().setCreateEntries(
                 Arrays.asList(constructCreateEntryWithSimpleParams("AccountName",
                         "Desc"))).build();
@@ -102,10 +81,6 @@
 
     @Test
     public void buildConstruct_nullList_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         assertThrows("Expected null list to throw NPE",
                 NullPointerException.class,
                 () -> new BeginCreateCredentialResponse.Builder().setCreateEntries(null).build()
@@ -114,9 +89,6 @@
 
     @Test
     public void getter_createEntry() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         String expectedAccountName = "AccountName";
         String expectedDescription = "Desc";
 
@@ -132,10 +104,6 @@
 
     @Test
     public void getter_remoteEntry_null() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         RemoteEntry expectedRemoteEntry = null;
         BeginCreateCredentialResponse response = new BeginCreateCredentialResponse(
                 Arrays.asList(constructCreateEntryWithSimpleParams("AccountName",
@@ -149,9 +117,6 @@
 
     @Test
     public void getter_remoteEntry_nonNull() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         RemoteEntry expectedRemoteEntry = constructRemoteEntryDefault();
 
         BeginCreateCredentialResponse response = new BeginCreateCredentialResponse(
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCredentialResponseTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCredentialResponseTest.kt
index 675a6c9..f15cd57 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCredentialResponseTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCredentialResponseTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.credentials.provider
 
-import androidx.core.os.BuildCompat
 import androidx.credentials.provider.ui.UiUtils.Companion.constructCreateEntryWithSimpleParams
 import androidx.credentials.provider.ui.UiUtils.Companion.constructRemoteEntryDefault
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,17 +25,13 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 26)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class BeginCreateCredentialResponseTest {
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         BeginCreateCredentialResponse(
             createEntries = listOf(
                 constructCreateEntryWithSimpleParams(
@@ -50,10 +45,6 @@
 
     @Test
     fun constructor_createEntriesOnly() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         BeginCreateCredentialResponse(
             createEntries = listOf(
                 constructCreateEntryWithSimpleParams(
@@ -66,10 +57,6 @@
 
     @Test
     fun constructor_remoteEntryOnly() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         BeginCreateCredentialResponse(
             remoteEntry = constructRemoteEntryDefault()
         )
@@ -77,9 +64,6 @@
 
     @Test
     fun getter_createEntry() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedAccountName = "AccountName"
         val expectedDescription = "Desc"
         val expectedSize = 1
@@ -102,10 +86,6 @@
 
     @Test
     fun getter_remoteEntry_null() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         val expectedRemoteEntry: RemoteEntry? = null
         val beginCreateCredentialResponse = BeginCreateCredentialResponse(
             listOf(
@@ -123,9 +103,6 @@
 
     @Test
     fun getter_remoteEntry_nonNull() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedRemoteEntry: RemoteEntry = constructRemoteEntryDefault()
 
         val beginCreateCredentialResponse = BeginCreateCredentialResponse(
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCustomCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCustomCredentialRequestJavaTest.java
index 77dbc37..ff3c9ec 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCustomCredentialRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCustomCredentialRequestJavaTest.java
@@ -23,7 +23,6 @@
 import android.content.pm.SigningInfo;
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
@@ -31,26 +30,18 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BeginCreateCustomCredentialRequestJavaTest {
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new BeginCreateCustomCredentialRequest("type", Bundle.EMPTY, null);
     }
 
     @Test
     public void constructor_nullTypeBundle_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         // TODO(b/275416815) - parameterize to account for all individually
         assertThrows("Expected null list to throw NPE",
                 NullPointerException.class,
@@ -62,10 +53,6 @@
 
     @Test
     public void constructor_emptyType_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         assertThrows("Expected empty type to throw IAE",
                 IllegalArgumentException.class,
                 () -> new BeginCreateCustomCredentialRequest("", Bundle.EMPTY,
@@ -76,9 +63,6 @@
 
     @Test
     public void getter_type() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         String expectedType = "ironman";
 
         BeginCreateCustomCredentialRequest beginCreateCustomCredentialRequest =
@@ -90,9 +74,6 @@
 
     @Test
     public void getter_bundle() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         String expectedKey = "query";
         String expectedValue = "data";
         Bundle expectedBundle = new Bundle();
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCustomCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCustomCredentialRequestTest.kt
index 81ce420..c4caf97 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCustomCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreateCustomCredentialRequestTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.pm.SigningInfo
 import android.os.Bundle
-import androidx.core.os.BuildCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
@@ -27,24 +26,18 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class BeginCreateCustomCredentialRequestTest {
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         BeginCreateCustomCredentialRequest("type", Bundle.EMPTY, null)
     }
 
     @Test
     fun constructor_emptyType_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         Assert.assertThrows(
             "Expected empty type to throw IAE",
             IllegalArgumentException::class.java
@@ -60,9 +53,6 @@
 
     @Test
     fun getter_type() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedType = "ironman"
         val beginCreateCustomCredentialRequest =
             BeginCreateCustomCredentialRequest(expectedType, Bundle.EMPTY, null)
@@ -72,9 +62,6 @@
 
     @Test
     fun getter_bundle() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedKey = "query"
         val expectedValue = "data"
         val expectedBundle = Bundle()
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePasswordRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePasswordRequestJavaTest.java
index b8c7790..468d755 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePasswordRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePasswordRequestJavaTest.java
@@ -21,45 +21,42 @@
 import android.content.pm.SigningInfo;
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.TestUtilsKt;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 28)
 @SmallTest
 public class BeginCreatePasswordRequestJavaTest {
     @Test
     public void constructor_success() {
-        if (BuildCompat.isAtLeastU()) {
-            new BeginCreatePasswordCredentialRequest(
-                    new CallingAppInfo("sample_package_name",
-                            new SigningInfo()),
-                    new Bundle());
-        }
+        new BeginCreatePasswordCredentialRequest(
+                new CallingAppInfo("sample_package_name",
+                        new SigningInfo()),
+                new Bundle());
     }
 
     @Test
     public void getter_callingAppInfo() {
-        if (BuildCompat.isAtLeastU()) {
-            Bundle expectedCandidateQueryBundle = new Bundle();
-            expectedCandidateQueryBundle.putString("key", "value");
-            String expectedPackageName = "sample_package_name";
-            SigningInfo expectedSigningInfo = new SigningInfo();
-            CallingAppInfo expectedCallingAppInfo = new CallingAppInfo(expectedPackageName,
-                    expectedSigningInfo);
+        Bundle expectedCandidateQueryBundle = new Bundle();
+        expectedCandidateQueryBundle.putString("key", "value");
+        String expectedPackageName = "sample_package_name";
+        SigningInfo expectedSigningInfo = new SigningInfo();
+        CallingAppInfo expectedCallingAppInfo = new CallingAppInfo(expectedPackageName,
+                expectedSigningInfo);
 
-            BeginCreatePasswordCredentialRequest request =
-                    new BeginCreatePasswordCredentialRequest(expectedCallingAppInfo,
-                            expectedCandidateQueryBundle);
+        BeginCreatePasswordCredentialRequest request =
+                new BeginCreatePasswordCredentialRequest(expectedCallingAppInfo,
+                        expectedCandidateQueryBundle);
 
-            assertThat(request.getCallingAppInfo().getPackageName()).isEqualTo(expectedPackageName);
-            assertThat(request.getCallingAppInfo().getSigningInfo()).isEqualTo(expectedSigningInfo);
-            TestUtilsKt.equals(request.getCandidateQueryData(), expectedCandidateQueryBundle);
-        }
+        assertThat(request.getCallingAppInfo().getPackageName()).isEqualTo(expectedPackageName);
+        assertThat(request.getCallingAppInfo().getSigningInfo()).isEqualTo(expectedSigningInfo);
+        TestUtilsKt.equals(request.getCandidateQueryData(), expectedCandidateQueryBundle);
     }
 
     // TODO ("Add framework conversion, createFrom tests")
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePasswordRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePasswordRequestTest.kt
index e0810b5..f18e823 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePasswordRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePasswordRequestTest.kt
@@ -17,24 +17,20 @@
 
 import android.content.pm.SigningInfo
 import android.os.Bundle
-import androidx.annotation.RequiresApi
-import androidx.core.os.BuildCompat
 import androidx.credentials.equals
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 28)
 @SmallTest
-@RequiresApi(34)
 class BeginCreatePasswordRequestTest {
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         BeginCreatePasswordCredentialRequest(
             CallingAppInfo(
                 "sample_package_name",
@@ -46,10 +42,6 @@
 
     @Test
     fun getter_callingAppInfo() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         val expectedCandidateQueryBundle = Bundle()
         expectedCandidateQueryBundle.putString("key", "value")
         val expectedPackageName = "sample_package_name"
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePublicKeyCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePublicKeyCredentialRequestJavaTest.java
index 29259d8..f2029ac 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePublicKeyCredentialRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePublicKeyCredentialRequestJavaTest.java
@@ -23,123 +23,110 @@
 import android.content.pm.SigningInfo;
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 28)
 @SmallTest
 public class BeginCreatePublicKeyCredentialRequestJavaTest {
     @Test
     public void constructor_emptyJson_throwsIllegalArgumentException() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows("Expected empty Json to throw error",
-                    IllegalArgumentException.class,
-                    () -> new BeginCreatePublicKeyCredentialRequest(
-                            "",
-                            new CallingAppInfo(
-                                    "sample_package_name", new SigningInfo()),
-                            new Bundle()
-                    )
-            );
-        }
+        assertThrows("Expected empty Json to throw error",
+                IllegalArgumentException.class,
+                () -> new BeginCreatePublicKeyCredentialRequest(
+                        "",
+                        new CallingAppInfo(
+                                "sample_package_name", new SigningInfo()),
+                        new Bundle()
+                )
+        );
+
     }
 
     @Test
     public void constructor_invalidJson_throwsIllegalArgumentException() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows("Expected invalid Json to throw error",
-                    IllegalArgumentException.class,
-                    () -> new BeginCreatePublicKeyCredentialRequest(
-                            "invalid",
-                            new CallingAppInfo(
-                                    "sample_package_name", new SigningInfo()),
-                            new Bundle()
-                    )
-            );
-        }
+        assertThrows("Expected invalid Json to throw error",
+                IllegalArgumentException.class,
+                () -> new BeginCreatePublicKeyCredentialRequest(
+                        "invalid",
+                        new CallingAppInfo(
+                                "sample_package_name", new SigningInfo()),
+                        new Bundle()
+                )
+        );
     }
 
     @Test
     public void constructor_nullJson_throwsNullPointerException() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows("Expected null Json to throw NPE",
-                    NullPointerException.class,
-                    () -> new BeginCreatePublicKeyCredentialRequest(
-                            null,
-                            new CallingAppInfo("sample_package_name",
-                                    new SigningInfo()),
-                            new Bundle()
-                    )
-            );
-        }
+        assertThrows("Expected null Json to throw NPE",
+                NullPointerException.class,
+                () -> new BeginCreatePublicKeyCredentialRequest(
+                        null,
+                        new CallingAppInfo("sample_package_name",
+                                new SigningInfo()),
+                        new Bundle()
+                )
+        );
     }
 
     @Test
     public void constructor_success() {
-        if (BuildCompat.isAtLeastU()) {
-            new BeginCreatePublicKeyCredentialRequest(
-                    "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                    new CallingAppInfo(
-                            "sample_package_name", new SigningInfo()
-                    ),
-                    new Bundle()
-            );
-        }
+        new BeginCreatePublicKeyCredentialRequest(
+                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
+                new CallingAppInfo(
+                        "sample_package_name", new SigningInfo()
+                ),
+                new Bundle()
+        );
     }
 
     @Test
     public void constructorWithClientDataHash_success() {
-        if (BuildCompat.isAtLeastU()) {
-            new BeginCreatePublicKeyCredentialRequest(
-                    "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                    new CallingAppInfo(
-                            "sample_package_name", new SigningInfo()
-                    ),
-                    new Bundle(),
-                    "client_data_hash".getBytes()
-            );
-        }
+        new BeginCreatePublicKeyCredentialRequest(
+                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
+                new CallingAppInfo(
+                        "sample_package_name", new SigningInfo()
+                ),
+                new Bundle(),
+                "client_data_hash".getBytes()
+        );
     }
 
     @Test
     public void getter_requestJson_success() {
-        if (BuildCompat.isAtLeastU()) {
-            String testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
+        String testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
 
-            BeginCreatePublicKeyCredentialRequest
-                    createPublicKeyCredentialReq = new BeginCreatePublicKeyCredentialRequest(
-                    testJsonExpected,
-                    new CallingAppInfo(
-                            "sample_package_name", new SigningInfo()),
-                    new Bundle()
-            );
+        BeginCreatePublicKeyCredentialRequest
+                createPublicKeyCredentialReq = new BeginCreatePublicKeyCredentialRequest(
+                testJsonExpected,
+                new CallingAppInfo(
+                        "sample_package_name", new SigningInfo()),
+                new Bundle()
+        );
 
-            String testJsonActual = createPublicKeyCredentialReq.getRequestJson();
-            assertThat(testJsonActual).isEqualTo(testJsonExpected);
-            assertThat(createPublicKeyCredentialReq.getClientDataHash()).isNull();
-
-        }
+        String testJsonActual = createPublicKeyCredentialReq.getRequestJson();
+        assertThat(testJsonActual).isEqualTo(testJsonExpected);
+        assertThat(createPublicKeyCredentialReq.getClientDataHash()).isNull();
     }
 
     @Test
     public void getter_clientDataHash_success() {
-        if (BuildCompat.isAtLeastU()) {
-            String testClientDataHashExpected = "client_data_hash";
-            BeginCreatePublicKeyCredentialRequest createPublicKeyCredentialReq =
-                    new BeginCreatePublicKeyCredentialRequest(
-                            "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                            new CallingAppInfo("sample_package_name",
-                                    new SigningInfo()),
-                            new Bundle(),
-                            testClientDataHashExpected.getBytes());
+        String testClientDataHashExpected = "client_data_hash";
+        BeginCreatePublicKeyCredentialRequest createPublicKeyCredentialReq =
+                new BeginCreatePublicKeyCredentialRequest(
+                        "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
+                        new CallingAppInfo("sample_package_name",
+                                new SigningInfo()),
+                        new Bundle(),
+                        testClientDataHashExpected.getBytes());
 
-            assertThat(createPublicKeyCredentialReq.getClientDataHash())
-                    .isEqualTo(testClientDataHashExpected.getBytes());
-        }
+        assertThat(createPublicKeyCredentialReq.getClientDataHash())
+                .isEqualTo(testClientDataHashExpected.getBytes());
     }
     // TODO ("Add framework conversion, createFrom & preferImmediatelyAvailable tests")
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePublicKeyCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePublicKeyCredentialRequestTest.kt
index 14a2143..8783adf 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePublicKeyCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginCreatePublicKeyCredentialRequestTest.kt
@@ -17,9 +17,8 @@
 
 import android.content.pm.SigningInfo
 import android.os.Bundle
-import androidx.annotation.RequiresApi
-import androidx.core.os.BuildCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert
@@ -28,13 +27,10 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-@RequiresApi(34)
+@SdkSuppress(minSdkVersion = 28)
 class BeginCreatePublicKeyCredentialRequestTest {
     @Test
     fun constructor_emptyJson_throwsIllegalArgumentException() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         Assert.assertThrows(
             "Expected empty Json to throw error",
             IllegalArgumentException::class.java
@@ -52,9 +48,6 @@
 
     @Test
     fun constructor_invalidJson_throwsIllegalArgumentException() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         Assert.assertThrows(
             "Expected invalid Json to throw error",
             IllegalArgumentException::class.java
@@ -72,9 +65,6 @@
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         BeginCreatePublicKeyCredentialRequest(
             "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
             CallingAppInfo(
@@ -86,9 +76,6 @@
 
     @Test
     fun constructorWithClientDataHash_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         BeginCreatePublicKeyCredentialRequest(
             "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
             CallingAppInfo(
@@ -101,9 +88,6 @@
 
     @Test
     fun getter_requestJson_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
 
         val createPublicKeyCredentialReq = BeginCreatePublicKeyCredentialRequest(
@@ -121,9 +105,6 @@
 
     @Test
     fun getter_clientDataHash_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val testClientDataHashExpected = "client_data_hash".toByteArray()
         val createPublicKeyCredentialReq = BeginCreatePublicKeyCredentialRequest(
             "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialRequestJavaTest.java
index b91f15a..4b3c63c 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialRequestJavaTest.java
@@ -23,7 +23,6 @@
 import android.content.pm.SigningInfo;
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
@@ -34,26 +33,18 @@
 import java.util.Collections;
 import java.util.List;
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BeginGetCredentialRequestJavaTest {
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new BeginGetCredentialRequest(Collections.emptyList(), null);
     }
 
     @Test
     public void constructor_nullList_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         assertThrows("Expected null list to throw NPE",
                 NullPointerException.class,
                 () -> new BeginGetCredentialRequest(null,
@@ -64,9 +55,6 @@
 
     @Test
     public void getter_beginGetCredentialOptions() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         String expectedKey = "query";
         String expectedValue = "data";
         Bundle expectedBundle = new Bundle();
@@ -98,9 +86,6 @@
 
     @Test
     public void getter_nullCallingAppInfo() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CallingAppInfo expectedCallingAppInfo = null;
 
         BeginGetCredentialRequest beginGetCredentialRequest =
@@ -113,9 +98,6 @@
 
     @Test
     public void getter_nonNullCallingAppInfo() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         String expectedPackageName = "john.wick.four.credentials";
         CallingAppInfo expectedCallingAppInfo = new CallingAppInfo(expectedPackageName,
                 new SigningInfo());
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialRequestTest.kt
index 3ccd7b3..4a495e3 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialRequestTest.kt
@@ -18,24 +18,24 @@
 
 import android.content.pm.SigningInfo
 import android.os.Bundle
-import androidx.core.os.BuildCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
+import org.junit.runner.RunWith
 
+@SdkSuppress(minSdkVersion = 28)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
 class BeginGetCredentialRequestTest {
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         BeginGetCredentialRequest(emptyList(), null)
     }
 
     @Test
     fun getter_beginGetCredentialOptions() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedKey = "query"
         val expectedValue = "data"
         val expectedBundle = Bundle()
@@ -66,9 +66,6 @@
 
     @Test
     fun getter_nullCallingAppInfo() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedCallingAppInfo: CallingAppInfo? = null
 
         val beginGetCredentialRequest = BeginGetCredentialRequest(
@@ -82,9 +79,6 @@
 
     @Test
     fun getter_nonNullCallingAppInfo() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedPackageName = "john.wick.four.credentials"
         val expectedCallingAppInfo = CallingAppInfo(
             expectedPackageName,
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialResponseJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialResponseJavaTest.java
index 8be08e4..5f3e93c 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialResponseJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialResponseJavaTest.java
@@ -25,7 +25,6 @@
 
 import static org.junit.Assert.assertThrows;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.PasswordCredential;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
@@ -36,27 +35,19 @@
 
 import java.util.Collections;
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BeginGetCredentialResponseJavaTest {
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new BeginGetCredentialResponse();
     }
 
     // TODO(b/275416815) - parameterize to account for all individually
     @Test
     public void constructor_nullList_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         assertThrows("Expected null list to throw NPE",
                 NullPointerException.class,
                 () -> new BeginGetCredentialResponse(
@@ -66,19 +57,11 @@
 
     @Test
     public void buildConstruct_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new BeginGetCredentialResponse.Builder().build();
     }
 
     @Test
     public void buildConstruct_nullList_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         assertThrows("Expected null list to throw NPE",
                 NullPointerException.class,
                 () -> new BeginGetCredentialResponse.Builder().setCredentialEntries(null)
@@ -88,9 +71,6 @@
 
     @Test
     public void getter_credentialEntries() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         int expectedSize = 1;
         String expectedType = PasswordCredential.TYPE_PASSWORD_CREDENTIAL;
         String expectedUsername = "f35";
@@ -111,9 +91,6 @@
 
     @Test
     public void getter_actionEntries() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         int expectedSize = 1;
         String expectedTitle = "boeing";
         String expectedSubtitle = "737max";
@@ -133,9 +110,6 @@
 
     @Test
     public void getter_authActionEntries() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         int expectedSize = 1;
         String expectedTitle = "boeing";
 
@@ -152,9 +126,6 @@
 
     @Test
     public void getter_remoteEntry_null() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         RemoteEntry expectedRemoteEntry = null;
 
         BeginGetCredentialResponse response = new BeginGetCredentialResponse(
@@ -168,9 +139,6 @@
 
     @Test
     public void getter_remoteEntry_nonNull() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         RemoteEntry expectedRemoteEntry = constructRemoteEntryDefault();
 
         BeginGetCredentialResponse response = new BeginGetCredentialResponse(
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialResponseTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialResponseTest.kt
index c985c92..9246bbc 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialResponseTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCredentialResponseTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.credentials.provider
 
-import androidx.core.os.BuildCompat
 import androidx.credentials.PasswordCredential
 import androidx.credentials.provider.ui.UiUtils.Companion.constructActionEntry
 import androidx.credentials.provider.ui.UiUtils.Companion.constructAuthenticationActionEntry
@@ -29,34 +28,23 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class BeginGetCredentialResponseTest {
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         BeginGetCredentialResponse()
     }
 
     @Test
     fun buildConstruct_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         BeginGetCredentialResponse.Builder().build()
     }
 
     @Test
     fun getter_credentialEntries() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedSize = 1
         val expectedType = PasswordCredential.TYPE_PASSWORD_CREDENTIAL
         val expectedUsername = "f35"
@@ -80,9 +68,6 @@
 
     @Test
     fun getter_actionEntries() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedSize = 1
         val expectedTitle = "boeing"
         val expectedSubtitle = "737max"
@@ -102,9 +87,6 @@
 
     @Test
     fun getter_authActionEntries() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedSize = 1
         val expectedTitle = "boeing"
 
@@ -122,10 +104,6 @@
 
     @Test
     fun getter_remoteEntry_null() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         val expectedRemoteEntry: RemoteEntry? = null
         val response = BeginGetCredentialResponse(
             emptyList(), emptyList(), emptyList(),
@@ -138,10 +116,6 @@
 
     @Test
     fun getter_remoteEntry_nonNull() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         val expectedRemoteEntry = constructRemoteEntryDefault()
         val response = BeginGetCredentialResponse(
             emptyList(), emptyList(), emptyList(),
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCustomCredentialOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCustomCredentialOptionJavaTest.java
index 1394cac..f03a59c 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCustomCredentialOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCustomCredentialOptionJavaTest.java
@@ -22,59 +22,55 @@
 
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.TestUtilsKt;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 28)
 @SmallTest
 public class BeginGetCustomCredentialOptionJavaTest {
     @Test
     public void constructor_success() {
-        if (BuildCompat.isAtLeastU()) {
-            Bundle expectedBundle = new Bundle();
-            expectedBundle.putString("random", "random_value");
-            String expectedType = "type";
-            String expectedId = "id";
+        Bundle expectedBundle = new Bundle();
+        expectedBundle.putString("random", "random_value");
+        String expectedType = "type";
+        String expectedId = "id";
 
-            BeginGetCustomCredentialOption option = new BeginGetCustomCredentialOption(
-                    expectedId, expectedType, expectedBundle);
+        BeginGetCustomCredentialOption option = new BeginGetCustomCredentialOption(
+                expectedId, expectedType, expectedBundle);
 
-            assertThat(option.getType()).isEqualTo(expectedType);
-            assertThat(option.getId()).isEqualTo(expectedId);
-            assertThat(TestUtilsKt.equals(option.getCandidateQueryData(), expectedBundle)).isTrue();
-        }
+        assertThat(option.getType()).isEqualTo(expectedType);
+        assertThat(option.getId()).isEqualTo(expectedId);
+        assertThat(TestUtilsKt.equals(option.getCandidateQueryData(), expectedBundle)).isTrue();
     }
 
     @Test
     public void constructor_emptyType_throwsIAE() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows("Expected empty Json to throw error",
-                    IllegalArgumentException.class,
-                    () -> new BeginGetCustomCredentialOption(
-                            "id",
-                            "",
-                            new Bundle()
-                    )
-            );
-        }
+
+        assertThrows("Expected empty Json to throw error",
+                IllegalArgumentException.class,
+                () -> new BeginGetCustomCredentialOption(
+                        "id",
+                        "",
+                        new Bundle()
+                )
+        );
     }
 
     @Test
     public void constructor_emptyId_throwsIAE() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows("Expected empty Json to throw error",
-                    IllegalArgumentException.class,
-                    () -> new BeginGetCustomCredentialOption(
-                            "",
-                            "type",
-                            new Bundle()
-                    )
-            );
-        }
+        assertThrows("Expected empty Json to throw error",
+                IllegalArgumentException.class,
+                () -> new BeginGetCustomCredentialOption(
+                        "",
+                        "type",
+                        new Bundle()
+                )
+        );
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCustomCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCustomCredentialOptionTest.kt
index f659b8c..ec8367b 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCustomCredentialOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetCustomCredentialOptionTest.kt
@@ -16,9 +16,9 @@
 package androidx.credentials.provider
 
 import android.os.Bundle
-import androidx.core.os.BuildCompat
 import androidx.credentials.equals
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth
 import org.junit.Assert.assertThrows
@@ -26,53 +26,48 @@
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 28)
 @SmallTest
 class BeginGetCustomCredentialOptionTest {
     @Test
     fun constructor_success() {
-        if (BuildCompat.isAtLeastU()) {
-            val expectedBundle = Bundle()
-            expectedBundle.putString("random", "random_value")
-            val expectedType = "type"
-            val expectedId = "id"
-            val option = BeginGetCustomCredentialOption(
-                expectedId, expectedType, expectedBundle
-            )
-            Truth.assertThat(option.type).isEqualTo(expectedType)
-            Truth.assertThat(option.id).isEqualTo(expectedId)
-            Truth.assertThat(equals(option.candidateQueryData, expectedBundle)).isTrue()
-        }
+        val expectedBundle = Bundle()
+        expectedBundle.putString("random", "random_value")
+        val expectedType = "type"
+        val expectedId = "id"
+        val option = BeginGetCustomCredentialOption(
+            expectedId, expectedType, expectedBundle
+        )
+        Truth.assertThat(option.type).isEqualTo(expectedType)
+        Truth.assertThat(option.id).isEqualTo(expectedId)
+        Truth.assertThat(equals(option.candidateQueryData, expectedBundle)).isTrue()
     }
 
     @Test
     fun constructor_emptyType_throwsIAE() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows(
-                "Expected empty Json to throw error",
-                IllegalArgumentException::class.java
-            ) {
-                BeginGetCustomCredentialOption(
-                    "id",
-                    "",
-                    Bundle()
-                )
-            }
+        assertThrows(
+            "Expected empty Json to throw error",
+            IllegalArgumentException::class.java
+        ) {
+            BeginGetCustomCredentialOption(
+                "id",
+                "",
+                Bundle()
+            )
         }
     }
 
     @Test
     fun constructor_emptyId_throwsIAE() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows(
-                "Expected empty Json to throw error",
-                IllegalArgumentException::class.java
-            ) {
-                BeginGetCustomCredentialOption(
-                    "",
-                    "type",
-                    Bundle()
-                )
-            }
+        assertThrows(
+            "Expected empty Json to throw error",
+            IllegalArgumentException::class.java
+        ) {
+            BeginGetCustomCredentialOption(
+                "",
+                "type",
+                Bundle()
+            )
         }
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPasswordOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPasswordOptionJavaTest.java
index 61713fc..cf3af28 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPasswordOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPasswordOptionJavaTest.java
@@ -20,11 +20,11 @@
 
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.GetPasswordOption;
 import androidx.credentials.PasswordCredential;
 import androidx.credentials.TestUtilsKt;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import com.google.common.collect.ImmutableSet;
@@ -36,27 +36,27 @@
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 28)
 @SmallTest
 public class BeginGetPasswordOptionJavaTest {
     private static final String BUNDLE_ID_KEY =
             "android.service.credentials.BeginGetCredentialOption.BUNDLE_ID_KEY";
     private static final String BUNDLE_ID = "id";
+
     @Test
-    public void getter_frameworkProperties() {
-        if (BuildCompat.isAtLeastU()) {
-            Set<String> expectedAllowedUserIds = ImmutableSet.of("id1", "id2", "id3");
-            Bundle bundle = new Bundle();
-            bundle.putStringArrayList(GetPasswordOption.BUNDLE_KEY_ALLOWED_USER_IDS,
-                    new ArrayList<>(expectedAllowedUserIds));
+    public void constructor_success() {
+        Set<String> expectedAllowedUserIds = ImmutableSet.of("id1", "id2", "id3");
+        Bundle bundle = new Bundle();
+        bundle.putStringArrayList(GetPasswordOption.BUNDLE_KEY_ALLOWED_USER_IDS,
+                new ArrayList<>(expectedAllowedUserIds));
 
-            BeginGetPasswordOption option = new BeginGetPasswordOption(expectedAllowedUserIds,
-                    bundle, BUNDLE_ID);
+        BeginGetPasswordOption option = new BeginGetPasswordOption(expectedAllowedUserIds,
+                bundle, BUNDLE_ID);
 
-            bundle.putString(BUNDLE_ID_KEY, BUNDLE_ID);
-            assertThat(option.getType()).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL);
-            assertThat(TestUtilsKt.equals(option.getCandidateQueryData(), bundle)).isTrue();
-            assertThat(option.getAllowedUserIds())
-                    .containsExactlyElementsIn(expectedAllowedUserIds);
-        }
+        bundle.putString(BUNDLE_ID_KEY, BUNDLE_ID);
+        assertThat(option.getType()).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL);
+        assertThat(TestUtilsKt.equals(option.getCandidateQueryData(), bundle)).isTrue();
+        assertThat(option.getAllowedUserIds())
+                .containsExactlyElementsIn(expectedAllowedUserIds);
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPasswordOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPasswordOptionTest.kt
index 556118c..f1b1206 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPasswordOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPasswordOptionTest.kt
@@ -16,8 +16,6 @@
 package androidx.credentials.provider
 
 import android.os.Bundle
-import androidx.annotation.RequiresApi
-import androidx.core.os.BuildCompat
 import androidx.credentials.GetPasswordOption
 import androidx.credentials.PasswordCredential
 import androidx.credentials.equals
@@ -29,7 +27,6 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-@RequiresApi(34)
 class BeginGetPasswordOptionTest {
     companion object {
         private const val BUNDLE_ID_KEY =
@@ -38,10 +35,7 @@
     }
 
     @Test
-    fun getter_frameworkProperties() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
+    fun constructor_success() {
         val expectedAllowedUserIds: Set<String> = setOf("id1", "id2", "id3")
         val bundle = Bundle()
         bundle.putStringArrayList(
@@ -58,11 +52,7 @@
     }
 
     @Test
-    fun getter_frameworkCreateFrom() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
+    fun createFrom_success() {
         val expectedAllowedUserIds: Set<String> = setOf("id1", "id2", "id3")
         val bundle = Bundle()
         bundle.putStringArrayList(
@@ -75,11 +65,7 @@
     }
 
     @Test
-    fun getter_frameworkCreateFromEntrySlice() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
+    fun createFromEntrySlice_success() {
         val expectedAllowedUserIds: Set<String> = setOf("id1", "id2", "id3")
         val bundle = Bundle()
         bundle.putStringArrayList(
@@ -87,7 +73,7 @@
             ArrayList(expectedAllowedUserIds)
         )
 
-        var option = BeginGetPasswordOption.createFrom(bundle, "id")
+        var option = BeginGetPasswordOption.createFromEntrySlice(bundle, "id")
         assertThat(option.id).isEqualTo("id")
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOptionJavaTest.java
index 282f13c..8cde656 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOptionJavaTest.java
@@ -24,7 +24,6 @@
 
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.GetPublicKeyCredentialOption;
 import androidx.credentials.PublicKeyCredential;
 import androidx.credentials.TestUtilsKt;
@@ -40,108 +39,93 @@
     private static final String BUNDLE_ID_KEY =
             "android.service.credentials.BeginGetCredentialOption.BUNDLE_ID_KEY";
     private static final String BUNDLE_ID = "id";
+
     @Test
     public void constructor_emptyJson_throwsIllegalArgumentException() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows("Expected empty Json to throw error",
-                    IllegalArgumentException.class,
-                    () -> new BeginGetPublicKeyCredentialOption(
-                            new Bundle(), "", "")
-            );
-        }
+        assertThrows("Expected empty Json to throw error",
+                IllegalArgumentException.class,
+                () -> new BeginGetPublicKeyCredentialOption(
+                        new Bundle(), "", "")
+        );
     }
 
     @Test
     public void constructor_invalidJson_throwsIllegalArgumentException() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows("Expected invalid Json to throw error",
-                    IllegalArgumentException.class,
-                    () -> new BeginGetPublicKeyCredentialOption(
-                            new Bundle(), "", "invalid")
-            );
-        }
+        assertThrows("Expected invalid Json to throw error",
+                IllegalArgumentException.class,
+                () -> new BeginGetPublicKeyCredentialOption(
+                        new Bundle(), "", "invalid")
+        );
     }
 
     @Test
     public void constructor_nullJson_throwsNullPointerException() {
-        if (BuildCompat.isAtLeastU()) {
-            assertThrows("Expected null Json to throw NPE",
-                    NullPointerException.class,
-                    () -> new BeginGetPublicKeyCredentialOption(
-                            new Bundle(), BUNDLE_ID, null)
-            );
-        }
+        assertThrows("Expected null Json to throw NPE",
+                NullPointerException.class,
+                () -> new BeginGetPublicKeyCredentialOption(
+                        new Bundle(), BUNDLE_ID, null)
+        );
     }
 
     @Test
     public void constructor_success() {
-        if (BuildCompat.isAtLeastU()) {
-            new BeginGetPublicKeyCredentialOption(
-                    new Bundle(), BUNDLE_ID,
-                    "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}");
-        }
+        new BeginGetPublicKeyCredentialOption(
+                new Bundle(), BUNDLE_ID,
+                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}");
     }
 
     @Test
     public void constructorWithClientDataHash_success() {
-        if (BuildCompat.isAtLeastU()) {
-            new BeginGetPublicKeyCredentialOption(
-                    new Bundle(), BUNDLE_ID,
-                    "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                    "client_data_hash".getBytes());
-        }
+        new BeginGetPublicKeyCredentialOption(
+                new Bundle(), BUNDLE_ID,
+                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
+                "client_data_hash".getBytes());
     }
 
     @Test
     public void getter_requestJson_success() {
-        if (BuildCompat.isAtLeastU()) {
-            String testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
+        String testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
 
-            BeginGetPublicKeyCredentialOption getPublicKeyCredentialOpt =
-                    new BeginGetPublicKeyCredentialOption(
-                            new Bundle(), BUNDLE_ID, testJsonExpected);
+        BeginGetPublicKeyCredentialOption getPublicKeyCredentialOpt =
+                new BeginGetPublicKeyCredentialOption(
+                        new Bundle(), BUNDLE_ID, testJsonExpected);
 
-            String testJsonActual = getPublicKeyCredentialOpt.getRequestJson();
-            assertThat(testJsonActual).isEqualTo(testJsonExpected);
-        }
+        String testJsonActual = getPublicKeyCredentialOpt.getRequestJson();
+        assertThat(testJsonActual).isEqualTo(testJsonExpected);
     }
 
     @Test
     public void getter_clientDataHash_success() {
-        if (BuildCompat.isAtLeastU()) {
-            byte[] testClientDataHashExpected = "client_data_hash".getBytes();
+        byte[] testClientDataHashExpected = "client_data_hash".getBytes();
 
-            BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOpt =
-                    new BeginGetPublicKeyCredentialOption(
-                            new Bundle(), BUNDLE_ID,
-                            "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                            testClientDataHashExpected);
+        BeginGetPublicKeyCredentialOption beginGetPublicKeyCredentialOpt =
+                new BeginGetPublicKeyCredentialOption(
+                        new Bundle(), BUNDLE_ID,
+                        "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
+                        testClientDataHashExpected);
 
-            byte[] testClientDataHashActual = beginGetPublicKeyCredentialOpt.getClientDataHash();
-            assertThat(testClientDataHashActual).isEqualTo(testClientDataHashExpected);
-        }
+        byte[] testClientDataHashActual = beginGetPublicKeyCredentialOpt.getClientDataHash();
+        assertThat(testClientDataHashActual).isEqualTo(testClientDataHashExpected);
     }
 
     @Test
     public void getter_frameworkProperties_success() {
-        if (BuildCompat.isAtLeastU()) {
-            String requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
-            byte[] clientDataHash = "client_data_hash".getBytes();
-            Bundle expectedData = new Bundle();
-            expectedData.putString(
-                    PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
-                    GetPublicKeyCredentialOption
-                            .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION);
-            expectedData.putString(BUNDLE_KEY_REQUEST_JSON, requestJsonExpected);
-            expectedData.putByteArray(GetPublicKeyCredentialOption.BUNDLE_KEY_CLIENT_DATA_HASH,
-                    clientDataHash);
+        String requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
+        byte[] clientDataHash = "client_data_hash".getBytes();
+        Bundle expectedData = new Bundle();
+        expectedData.putString(
+                PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
+                GetPublicKeyCredentialOption
+                        .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION);
+        expectedData.putString(BUNDLE_KEY_REQUEST_JSON, requestJsonExpected);
+        expectedData.putByteArray(GetPublicKeyCredentialOption.BUNDLE_KEY_CLIENT_DATA_HASH,
+                clientDataHash);
 
-            BeginGetPublicKeyCredentialOption option = new BeginGetPublicKeyCredentialOption(
-                    expectedData, BUNDLE_ID, requestJsonExpected, clientDataHash);
+        BeginGetPublicKeyCredentialOption option = new BeginGetPublicKeyCredentialOption(
+                expectedData, BUNDLE_ID, requestJsonExpected, clientDataHash);
 
-            expectedData.putString(BUNDLE_ID_KEY, BUNDLE_ID);
-            assertThat(option.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
-            assertThat(TestUtilsKt.equals(option.getCandidateQueryData(), expectedData)).isTrue();
-        }
+        expectedData.putString(BUNDLE_ID_KEY, BUNDLE_ID);
+        assertThat(option.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
+        assertThat(TestUtilsKt.equals(option.getCandidateQueryData(), expectedData)).isTrue();
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOptionTest.kt
index 9139dda..7a410f4 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOptionTest.kt
@@ -16,8 +16,6 @@
 package androidx.credentials.provider
 
 import android.os.Bundle
-import androidx.annotation.RequiresApi
-import androidx.core.os.BuildCompat
 import androidx.credentials.GetPublicKeyCredentialOption
 import androidx.credentials.PublicKeyCredential
 import androidx.credentials.equals
@@ -30,7 +28,6 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-@RequiresApi(34)
 class BeginGetPublicKeyCredentialOptionTest {
     companion object {
         private const val BUNDLE_ID_KEY =
@@ -39,9 +36,6 @@
     }
     @Test
     fun constructor_emptyJson_throwsIllegalArgumentException() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         Assert.assertThrows(
             "Expected empty Json to throw error",
             IllegalArgumentException::class.java
@@ -52,9 +46,6 @@
 
     @Test
     fun constructor_invalidJson_throwsIllegalArgumentException() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         Assert.assertThrows(
             "Expected invalid Json to throw error",
             IllegalArgumentException::class.java
@@ -65,9 +56,6 @@
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         BeginGetPublicKeyCredentialOption(
             Bundle(), BUNDLE_ID, "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
             "client_data_hash".toByteArray()
@@ -76,9 +64,6 @@
 
     @Test
     fun getter_clientDataHash_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val testClientDataHashExpected = "client_data_hash".toByteArray()
 
         val beginGetPublicKeyCredentialOpt = BeginGetPublicKeyCredentialOption(
@@ -92,9 +77,6 @@
 
     @Test
     fun getter_requestJson_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
 
         val getPublicKeyCredentialOpt = BeginGetPublicKeyCredentialOption(
@@ -107,9 +89,6 @@
 
     @Test
     fun getter_frameworkProperties_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
         val clientDataHash = "client_data_hash".toByteArray()
         val expectedData = Bundle()
@@ -131,11 +110,7 @@
     }
 
     @Test
-    fun getter_frameworkCreateFrom() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
+    fun createFrom_success() {
         val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
         val clientDataHash = "client_data_hash".toByteArray()
         val bundle = Bundle()
@@ -154,11 +129,7 @@
     }
 
     @Test
-    fun getter_frameworkCreateFromEntrySlice() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
+    fun createFromEntrySlice_success() {
         val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
         val clientDataHash = "client_data_hash".toByteArray()
         val bundle = Bundle()
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CallingAppInfoJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CallingAppInfoJavaTest.java
index be8bd0f..05a75fe 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CallingAppInfoJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CallingAppInfoJavaTest.java
@@ -18,7 +18,6 @@
 
 import android.content.pm.SigningInfo;
 
-import androidx.core.os.BuildCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
@@ -29,66 +28,40 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 public class CallingAppInfoJavaTest {
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new CallingAppInfo("name", new SigningInfo());
     }
 
     @Test
     public void constructor_success_withOrigin() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new CallingAppInfo("name", new SigningInfo(), "origin");
     }
 
     @Test
     public void constructor_fail_emptyPackageName() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         Assert.assertThrows(
                 "Expected exception from no package name",
                 IllegalArgumentException.class,
-                () -> {
-                    new CallingAppInfo("", new SigningInfo(), "origin");
-                });
+                () -> new CallingAppInfo("", new SigningInfo(), "origin"));
     }
 
     @Test
     public void constructor_fail_nullPackageName() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         Assert.assertThrows(
                 "Expected exception from null package name",
                 NullPointerException.class,
-                () -> {
-                    new CallingAppInfo(null, new SigningInfo(), "origin");
-                });
+                () -> new CallingAppInfo(null, new SigningInfo(), "origin"));
     }
 
     @Test
     public void constructor_fail_nullSigningInfo() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         Assert.assertThrows(
                 "Expected exception from null signing info",
                 NullPointerException.class,
-                () -> {
-                    new CallingAppInfo("package", null, "origin");
-                });
+                () -> new CallingAppInfo("package", null, "origin"));
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CallingAppInfoTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CallingAppInfoTest.kt
index bbd22fe..2e04b47 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CallingAppInfoTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CallingAppInfoTest.kt
@@ -17,7 +17,6 @@
 package androidx.credentials.provider
 
 import android.content.pm.SigningInfo
-import androidx.core.os.BuildCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
@@ -25,35 +24,23 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
- @SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = 28)
  @RunWith(AndroidJUnit4::class)
  @SmallTest
  class CallingAppInfoTest {
 
      @Test
      fun constructor_success() {
-         if (!BuildCompat.isAtLeastU()) {
-             return
-         }
-
          CallingAppInfo("name", SigningInfo())
      }
 
      @Test
      fun constructor_success_withOrigin() {
-         if (!BuildCompat.isAtLeastU()) {
-             return
-         }
-
          CallingAppInfo("name", SigningInfo(), "origin")
      }
 
      @Test
      fun constructor_fail_emptyPackageName() {
-         if (!BuildCompat.isAtLeastU()) {
-             return
-         }
-
          Assert.assertThrows(
             "Expected exception from no package name",
             IllegalArgumentException::class.java
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialEntryTest.kt
index 387f712..66fc41e 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialEntryTest.kt
@@ -16,62 +16,91 @@
 
 package androidx.credentials.provider
 
-import android.app.slice.Slice
-import android.app.slice.SliceSpec
-import android.net.Uri
-import androidx.core.os.BuildCompat
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
 import androidx.credentials.PasswordCredential.Companion.TYPE_PASSWORD_CREDENTIAL
 import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertNotNull
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 @RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 28)
 @SmallTest
 class CredentialEntryTest {
+    private val mContext = ApplicationProvider.getApplicationContext<Context>()
+    private val mIntent = Intent()
+    private val mPendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
+        PendingIntent.FLAG_IMMUTABLE)
+
+    companion object {
+        private val BEGIN_OPTION_CUSTOM: BeginGetCredentialOption = BeginGetCustomCredentialOption(
+            "id", "custom", Bundle()
+        )
+        private val BEGIN_OPTION_PASSWORD: BeginGetPasswordOption = BeginGetPasswordOption(
+            emptySet(), Bundle.EMPTY, "id"
+        )
+        private val BEGIN_OPTION_PUBLIC_KEY: BeginGetPublicKeyCredentialOption =
+            BeginGetPublicKeyCredentialOption(
+                Bundle.EMPTY, "id", "{\"key1\":{\"key2\":{\"key3\":\"value3\"}}}"
+        )
+    }
 
     @Test
     fun createFrom_passwordCredential() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
+        val entry = PasswordCredentialEntry(
+            mContext,
+            "username",
+            mPendingIntent,
+            BEGIN_OPTION_PASSWORD
+        )
+        assertNotNull(entry)
 
-        var sliceSpec = SliceSpec(TYPE_PASSWORD_CREDENTIAL, 1)
-        var slice = Slice.Builder(Uri.EMPTY, sliceSpec).build()
+        val slice = PasswordCredentialEntry.toSlice(entry)
+        assertNotNull(slice)
 
-        var result = CredentialEntry.createFrom(slice)
+        val result = CredentialEntry.createFrom(slice!!)
         assertThat(result).isNotNull()
         assertThat(result!!.type).isEqualTo(TYPE_PASSWORD_CREDENTIAL)
     }
 
     @Test
     fun createFrom_publicKeyCredential() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
+        val entry = PublicKeyCredentialEntry(
+            mContext,
+            "username",
+            mPendingIntent,
+            BEGIN_OPTION_PUBLIC_KEY
+        )
+        assertNotNull(entry)
 
-        var sliceSpec = SliceSpec(TYPE_PUBLIC_KEY_CREDENTIAL, 1)
-        var slice = Slice.Builder(Uri.EMPTY, sliceSpec).build()
+        val slice = PublicKeyCredentialEntry.toSlice(entry)
+        assertNotNull(slice)
 
-        var result = CredentialEntry.createFrom(slice)
-        assertThat(result).isNotNull()
+        val result = CredentialEntry.createFrom(slice!!)
+        assertNotNull(result)
         assertThat(result!!.type).isEqualTo(TYPE_PUBLIC_KEY_CREDENTIAL)
     }
 
     @Test
     fun createFrom_customCredential() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
+        val entry = CustomCredentialEntry(
+            mContext,
+            "title",
+            mPendingIntent,
+            BEGIN_OPTION_CUSTOM
+        )
+        val slice = CustomCredentialEntry.toSlice(entry)
+        assertNotNull(slice)
 
-        var sliceSpec = SliceSpec("custom", 1)
-        var slice = Slice.Builder(Uri.EMPTY, sliceSpec).build()
-
-        var result = CredentialEntry.createFrom(slice)
+        val result = CredentialEntry.createFrom(slice!!)
         assertThat(result).isNotNull()
         assertThat(result!!.type).isEqualTo("custom")
     }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceJavaTest.java
new file mode 100644
index 0000000..6f0606f
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceJavaTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.provider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.SigningInfo;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import androidx.core.os.BuildCompat;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+public class CredentialProviderServiceJavaTest {
+
+    @Test
+    public void test_createRequest() {
+        if (!BuildCompat.isAtLeastU()) {
+            return;
+        }
+
+        CredentialProviderServiceTestImpl service = new CredentialProviderServiceTestImpl();
+        service.setTestMode(true);
+
+        android.service.credentials.BeginCreateCredentialRequest request =
+                new android.service.credentials.BeginCreateCredentialRequest("test", new Bundle());
+        OutcomeReceiver<
+                        android.service.credentials.BeginCreateCredentialResponse,
+                        android.credentials.CreateCredentialException>
+                outcome =
+                        new OutcomeReceiver<
+                                android.service.credentials.BeginCreateCredentialResponse,
+                                android.credentials.CreateCredentialException>() {
+                    public void onResult(
+                                    android.service.credentials.BeginCreateCredentialResponse
+                                            response) {}
+
+                    public void onError(
+                                    android.credentials.CreateCredentialException error) {}
+                };
+
+        // Call the service.
+        assertThat(service.getLastCreateRequest()).isNull();
+        service.onBeginCreateCredential(request, new CancellationSignal(), outcome);
+        assertThat(service.getLastCreateRequest()).isNotNull();
+    }
+
+    @Test
+    public void test_getRequest() {
+        if (!BuildCompat.isAtLeastU()) {
+            return;
+        }
+
+        CredentialProviderServiceTestImpl service = new CredentialProviderServiceTestImpl();
+        service.setTestMode(true);
+
+        BeginGetCredentialRequest request =
+                new BeginGetCredentialRequest(new ArrayList<BeginGetCredentialOption>());
+        OutcomeReceiver<
+                        androidx.credentials.provider.BeginGetCredentialResponse,
+                        androidx.credentials.exceptions.GetCredentialException>
+                outcome =
+                        new OutcomeReceiver<
+                                androidx.credentials.provider.BeginGetCredentialResponse,
+                                androidx.credentials.exceptions.GetCredentialException>() {
+                    public void onResult(
+                                    androidx.credentials.provider.BeginGetCredentialResponse
+                                            response) {}
+
+                    public void onError(
+                                    androidx.credentials.exceptions.GetCredentialException error) {}
+                };
+
+        // Call the service.
+        assertThat(service.getLastGetRequest()).isNull();
+        service.onBeginGetCredentialRequest(request, new CancellationSignal(), outcome);
+        assertThat(service.getLastGetRequest()).isNotNull();
+    }
+
+    @Test
+    public void test_clearRequest() {
+        if (!BuildCompat.isAtLeastU()) {
+            return;
+        }
+
+        CredentialProviderServiceTestImpl service = new CredentialProviderServiceTestImpl();
+        service.setTestMode(true);
+
+        ProviderClearCredentialStateRequest request =
+                new ProviderClearCredentialStateRequest(
+                        new CallingAppInfo("name", new SigningInfo()));
+        OutcomeReceiver<Void, androidx.credentials.exceptions.ClearCredentialException> outcome =
+                new OutcomeReceiver<
+                        Void, androidx.credentials.exceptions.ClearCredentialException>() {
+                    public void onResult(Void response) {}
+
+                    public void onError(
+                            androidx.credentials.exceptions.ClearCredentialException error) {}
+                };
+
+        // Call the service.
+        assertThat(service.getLastClearRequest()).isNull();
+        service.onClearCredentialStateRequest(request, new CancellationSignal(), outcome);
+        assertThat(service.getLastClearRequest()).isNotNull();
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTest.kt
new file mode 100644
index 0000000..a863383
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.provider
+
+import android.content.pm.SigningInfo
+import android.os.Bundle
+import android.os.CancellationSignal
+import android.os.OutcomeReceiver
+import android.util.Log
+import androidx.core.os.BuildCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CredentialProviderServiceTest {
+
+    private val LOG_TAG = "CredentialProviderServiceTest"
+
+    @Test
+    fun test_createRequest() {
+        if (!BuildCompat.isAtLeastU()) {
+            return
+        }
+
+        var service = CredentialProviderServiceTestImpl()
+        service.isTestMode = true
+
+        var request = android.service.credentials.BeginCreateCredentialRequest("test", Bundle())
+        val outcome = OutcomeReceiver<android.service.credentials.BeginCreateCredentialResponse,
+                android.credentials.CreateCredentialException> {
+            fun onResult(response: android.service.credentials.BeginCreateCredentialResponse) {
+                Log.i(LOG_TAG, "create request: " + response.toString())
+            }
+            fun onError(error: android.credentials.CreateCredentialException) {
+                Log.e(LOG_TAG, "create request error", error)
+            }
+        }
+
+        // Call the service.
+        assertThat(service.lastCreateRequest).isNull()
+        service.onBeginCreateCredential(request, CancellationSignal(), outcome)
+        assertThat(service.lastCreateRequest).isNotNull()
+    }
+
+    @Test
+    fun test_getRequest() {
+        if (!BuildCompat.isAtLeastU()) {
+            return
+        }
+
+        var service = CredentialProviderServiceTestImpl()
+        service.isTestMode = true
+
+        var request = BeginGetCredentialRequest(listOf<BeginGetCredentialOption>())
+        val outcome = OutcomeReceiver<androidx.credentials.provider.BeginGetCredentialResponse,
+                androidx.credentials.exceptions.GetCredentialException> {
+            fun onResult(response: androidx.credentials.provider.BeginGetCredentialResponse) {
+                Log.i(LOG_TAG, "get request: " + response.toString())
+            }
+            fun onError(error: androidx.credentials.exceptions.GetCredentialException) {
+                Log.e(LOG_TAG, "get request error", error)
+            }
+        }
+
+        // Call the service.
+        assertThat(service.lastGetRequest).isNull()
+        service.onBeginGetCredentialRequest(request, CancellationSignal(), outcome)
+        assertThat(service.lastGetRequest).isNotNull()
+    }
+
+    @Test
+    fun test_clearRequest() {
+        if (!BuildCompat.isAtLeastU()) {
+            return
+        }
+
+        var service = CredentialProviderServiceTestImpl()
+        service.isTestMode = true
+
+        var request = ProviderClearCredentialStateRequest(CallingAppInfo("name", SigningInfo()))
+        val outcome = OutcomeReceiver<Void?,
+                androidx.credentials.exceptions.ClearCredentialException> {
+            fun onResult(response: Void?) {
+                Log.i(LOG_TAG, "clear request: " + response.toString())
+            }
+            fun onError(error: androidx.credentials.exceptions.ClearCredentialException) {
+                Log.e(LOG_TAG, "clear request error", error)
+            }
+        }
+
+        // Call the service.
+        assertThat(service.lastClearRequest).isNull()
+        service.onClearCredentialStateRequest(request, CancellationSignal(), outcome)
+        assertThat(service.lastClearRequest).isNotNull()
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTestImpl.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTestImpl.kt
new file mode 100644
index 0000000..5459287
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/CredentialProviderServiceTestImpl.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.provider
+
+import android.credentials.GetCredentialException
+import android.os.CancellationSignal
+import android.os.OutcomeReceiver
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.credentials.exceptions.ClearCredentialException
+import androidx.credentials.exceptions.CreateCredentialException
+
+@RequiresApi(34)
+class CredentialProviderServiceTestImpl : CredentialProviderService() {
+
+    private val LOG_TAG = "CredentialProviderServiceTest"
+
+    public final override fun onClearCredentialStateRequest(
+        request: ProviderClearCredentialStateRequest,
+        cancellationSignal: CancellationSignal,
+        callback: OutcomeReceiver<Void?,
+            ClearCredentialException>
+    ) {
+        Log.i(LOG_TAG, "onClearCredentialStateRequest")
+    }
+
+    public final override fun onBeginGetCredentialRequest(
+        request: BeginGetCredentialRequest,
+        cancellationSignal: CancellationSignal,
+        callback: OutcomeReceiver<BeginGetCredentialResponse,
+            androidx.credentials.exceptions.GetCredentialException>
+    ) {
+        Log.i(LOG_TAG, "onBeginGetCredentialRequest")
+    }
+
+    public final override fun onBeginCreateCredentialRequest(
+        request: BeginCreateCredentialRequest,
+        cancellationSignal: CancellationSignal,
+        callback: OutcomeReceiver<BeginCreateCredentialResponse,
+            CreateCredentialException>
+    ) {
+        Log.i(LOG_TAG, "onBeginCreateCredentialRequest")
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderClearCredentialStateRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderClearCredentialStateRequestJavaTest.java
index ea7e5bf..d03c77a 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderClearCredentialStateRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderClearCredentialStateRequestJavaTest.java
@@ -30,7 +30,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class ProviderClearCredentialStateRequestJavaTest {
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderClearCredentialStateRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderClearCredentialStateRequestTest.kt
index d5b9a6a..63f06b6 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderClearCredentialStateRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderClearCredentialStateRequestTest.kt
@@ -17,7 +17,6 @@
 package androidx.credentials.provider
 
 import android.content.pm.SigningInfo
-import androidx.annotation.RequiresApi
 import androidx.credentials.equals
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
@@ -26,12 +25,11 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ProviderClearCredentialStateRequestTest {
 
-    @RequiresApi(34)
     @Test
     fun testConstructor_success() {
         val callingAppInfo = CallingAppInfo("sample_package_name", SigningInfo())
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderCreateCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderCreateCredentialRequestJavaTest.java
index b8b4cbe..44cafe2 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderCreateCredentialRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderCreateCredentialRequestJavaTest.java
@@ -21,7 +21,6 @@
 
 import android.content.pm.SigningInfo;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.CreatePasswordRequest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
@@ -30,17 +29,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class ProviderCreateCredentialRequestJavaTest {
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         CreatePasswordRequest request = new CreatePasswordRequest("id", "password");
 
         new ProviderCreateCredentialRequest(request, new CallingAppInfo("name", new SigningInfo()));
@@ -48,10 +43,6 @@
 
     @Test
     public void constructor_nullInputs_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         assertThrows(
                 "Expected null list to throw NPE",
                 NullPointerException.class,
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderCreateCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderCreateCredentialRequestTest.kt
index 075e5cb..09f7864 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderCreateCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderCreateCredentialRequestTest.kt
@@ -17,7 +17,6 @@
 package androidx.credentials.provider
 
 import android.content.pm.SigningInfo
-import androidx.core.os.BuildCompat
 import androidx.credentials.CreatePasswordRequest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
@@ -25,17 +24,13 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
- @SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = 28)
  @RunWith(AndroidJUnit4::class)
  @SmallTest
  class ProviderCreateCredentialRequestTest {
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         val request = CreatePasswordRequest("id", "password")
 
         ProviderCreateCredentialRequest(
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderGetCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderGetCredentialRequestJavaTest.java
index e0172a2..7db7eb5 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderGetCredentialRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderGetCredentialRequestJavaTest.java
@@ -24,7 +24,6 @@
 import android.content.pm.SigningInfo;
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.CredentialOption;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
@@ -39,17 +38,13 @@
 import java.util.List;
 import java.util.Set;
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class ProviderGetCredentialRequestJavaTest {
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         new ProviderGetCredentialRequest(
                 Collections.singletonList(CredentialOption.createFrom("type", new Bundle(),
                         new Bundle(), true, ImmutableSet.of())),
@@ -59,10 +54,6 @@
 
     @Test
     public void constructor_nullInputs_throws() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
-
         assertThrows("Expected null list to throw NPE",
                 NullPointerException.class,
                 () -> new ProviderGetCredentialRequest(null, null)
@@ -71,9 +62,6 @@
 
     @Test
     public void getter_credentialOptions() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         String expectedType = "BoeingCred";
         String expectedQueryKey = "PilotName";
         String expectedQueryValue = "PilotPassword";
@@ -120,9 +108,6 @@
 
     @Test
     public void getter_signingInfo() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         String expectedPackageName = "cool.security.package";
 
         ProviderGetCredentialRequest providerGetCredentialRequest =
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderGetCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderGetCredentialRequestTest.kt
index a3f21fe..62e4b77 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderGetCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ProviderGetCredentialRequestTest.kt
@@ -19,7 +19,6 @@
 import android.content.ComponentName
 import android.content.pm.SigningInfo
 import android.os.Bundle
-import androidx.core.os.BuildCompat
 import androidx.credentials.CredentialOption.Companion.createFrom
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
@@ -28,17 +27,13 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 28)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ProviderGetCredentialRequestTest {
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
-
         ProviderGetCredentialRequest(
             listOf(
                 createFrom(
@@ -57,9 +52,6 @@
 
     @Test
     fun getter_credentialOptions() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedType = "BoeingCred"
         val expectedQueryKey = "PilotName"
         val expectedQueryValue = "PilotPassword"
@@ -109,9 +101,6 @@
 
     @Test
     fun getter_signingInfo() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val expectedPackageName = "cool.security.package"
 
         val providerGetCredentialRequest = ProviderGetCredentialRequest(
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/ActionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/ActionJavaTest.java
index d0bc879..e75bbf6 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/ActionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/ActionJavaTest.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.content.Intent;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.provider.Action;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,7 +37,6 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 public class ActionJavaTest {
     private static final CharSequence TITLE = "title";
     private static final CharSequence SUBTITLE = "subtitle";
@@ -51,9 +49,6 @@
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         Action action = new Action(TITLE, mPendingIntent, SUBTITLE);
 
         assertNotNull(action);
@@ -64,9 +59,6 @@
 
     @Test
     public void constructor_nullTitle_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null title to throw NPE",
                 NullPointerException.class,
                 () -> new Action(null, mPendingIntent, SUBTITLE));
@@ -74,9 +66,6 @@
 
     @Test
     public void constructor_nullPendingIntent_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null title to throw NPE",
                 NullPointerException.class,
                 () -> new Action(TITLE, null, SUBTITLE));
@@ -84,19 +73,14 @@
 
     @Test
     public void constructor_emptyTitle_throwsIllegalArgumentException() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected empty title to throw IllegalArgumentException",
                 IllegalArgumentException.class,
                 () -> new Action("", mPendingIntent, SUBTITLE));
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         Action originalAction = new Action(TITLE, mPendingIntent, SUBTITLE);
         Slice slice = Action.toSlice(originalAction);
 
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/ActionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/ActionTest.kt
index c075468..102ef17 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/ActionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/ActionTest.kt
@@ -18,7 +18,6 @@
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
-import androidx.core.os.BuildCompat
 import androidx.credentials.provider.Action
 import androidx.credentials.provider.Action.Companion.fromSlice
 import androidx.test.core.app.ApplicationProvider
@@ -33,7 +32,6 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 class ActionTest {
     private val mContext = ApplicationProvider.getApplicationContext<Context>()
     private val mIntent = Intent()
@@ -42,14 +40,9 @@
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val action = Action(TITLE, mPendingIntent, SUBTITLE)
-        val slice = Action.toSlice(action)
 
         assertNotNull(action)
-        assertNotNull(slice)
         assertThat(TITLE == action.title)
         assertThat(SUBTITLE == action.subtitle)
         assertThat(mPendingIntent === action.pendingIntent)
@@ -57,9 +50,6 @@
 
     @Test
     fun constructor_emptyTitle_throwsIllegalArgumentException() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         Assert.assertThrows(
             "Expected empty title to throw IllegalArgumentException",
             IllegalArgumentException::class.java
@@ -67,10 +57,8 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val originalAction = Action(TITLE, mPendingIntent, SUBTITLE)
         val slice = Action.toSlice(originalAction)
 
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/AuthenticationActionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/AuthenticationActionJavaTest.java
index e1c6a5d..702baea 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/AuthenticationActionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/AuthenticationActionJavaTest.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.content.Intent;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.provider.AuthenticationAction;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,7 +37,6 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 public class AuthenticationActionJavaTest {
     private static final CharSequence TITLE = "title";
     private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -48,9 +46,6 @@
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         AuthenticationAction action = new AuthenticationAction(TITLE, mPendingIntent);
 
         assertThat(mPendingIntent == action.getPendingIntent());
@@ -58,9 +53,6 @@
 
     @Test
     public void build_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         AuthenticationAction action =
                 new AuthenticationAction.Builder(TITLE, mPendingIntent).build();
 
@@ -69,9 +61,6 @@
 
     @Test
     public void constructor_nullPendingIntent_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null pending intent to throw NPE",
                 NullPointerException.class,
                 () -> new AuthenticationAction(TITLE, null));
@@ -79,19 +68,14 @@
 
     @Test
     public void constructor_emptyTitle_throwsIllegalArgumentException() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected empty title to throw IAE",
                 IllegalArgumentException.class,
                 () -> new AuthenticationAction("", mPendingIntent));
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         AuthenticationAction originalAction = new AuthenticationAction(TITLE, mPendingIntent);
         Slice slice = AuthenticationAction.toSlice(originalAction);
 
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/AuthenticationActionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/AuthenticationActionTest.kt
index a4377fe..8c13327 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/AuthenticationActionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/AuthenticationActionTest.kt
@@ -18,7 +18,6 @@
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
-import androidx.core.os.BuildCompat
 import androidx.credentials.provider.AuthenticationAction
 import androidx.credentials.provider.AuthenticationAction.Companion.fromSlice
 import androidx.test.core.app.ApplicationProvider
@@ -33,7 +32,6 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 class AuthenticationActionTest {
     private val mContext = ApplicationProvider.getApplicationContext<Context>()
     private val mIntent = Intent()
@@ -42,9 +40,6 @@
 
     @Test
     fun build_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val action = AuthenticationAction.Builder(TITLE, mPendingIntent).build()
 
         assertThat(mPendingIntent).isEqualTo(action.pendingIntent)
@@ -52,9 +47,6 @@
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val action = AuthenticationAction(TITLE, mPendingIntent)
 
         assertThat(mPendingIntent).isEqualTo(action.pendingIntent)
@@ -62,9 +54,6 @@
 
     @Test
     fun constructor_emptyTitle_throwsIllegalArgumentException() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         Assert.assertThrows(
             "Expected empty title to throw IAE",
             IllegalArgumentException::class.java
@@ -72,10 +61,8 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val originalAction = AuthenticationAction(TITLE, mPendingIntent)
         val slice = AuthenticationAction.toSlice(originalAction)
 
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryJavaTest.java
index f2992da..cb9397d 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryJavaTest.java
@@ -28,7 +28,6 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.provider.CreateEntry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -41,8 +40,8 @@
 import java.time.Instant;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 26)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 public class CreateEntryJavaTest {
     private static final CharSequence ACCOUNT_NAME = "account_name";
     private static final int PASSWORD_COUNT = 10;
@@ -61,9 +60,6 @@
 
     @Test
     public void constructor_requiredParameters_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CreateEntry entry = constructEntryWithRequiredParams();
 
         assertNotNull(entry);
@@ -77,9 +73,6 @@
 
     @Test
     public void constructor_allParameters_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CreateEntry entry = constructEntryWithAllParams();
 
         assertNotNull(entry);
@@ -88,9 +81,6 @@
 
     @Test
     public void constructor_nullAccountName_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null title to throw NPE",
                 NullPointerException.class,
                 () -> new CreateEntry.Builder(
@@ -99,9 +89,6 @@
 
     @Test
     public void constructor_nullPendingIntent_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null pending intent to throw NPE",
                 NullPointerException.class,
                 () -> new CreateEntry.Builder(ACCOUNT_NAME, null).build());
@@ -109,19 +96,14 @@
 
     @Test
     public void constructor_emptyAccountName_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected empty account name to throw NPE",
                 IllegalArgumentException.class,
                 () -> new CreateEntry.Builder("", mPendingIntent).build());
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_requiredParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CreateEntry originalEntry = constructEntryWithRequiredParams();
 
         CreateEntry entry = CreateEntry.fromSlice(
@@ -132,10 +114,8 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CreateEntry originalEntry = constructEntryWithAllParams();
 
         CreateEntry entry = CreateEntry.fromSlice(
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryTest.kt
index 13417bd..b363f1d 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CreateEntryTest.kt
@@ -20,7 +20,6 @@
 import android.content.Intent
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
-import androidx.core.os.BuildCompat
 import androidx.credentials.provider.CreateEntry
 import androidx.credentials.provider.CreateEntry.Companion.fromSlice
 import androidx.test.core.app.ApplicationProvider
@@ -36,8 +35,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 @RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 26)
 @SmallTest
 class CreateEntryTest {
     private val mContext = ApplicationProvider.getApplicationContext<Context>()
@@ -49,9 +48,6 @@
 
     @Test
     fun constructor_success_autoSelectDefaultFalse() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructEntryWithRequiredParams()
 
         assertNotNull(entry)
@@ -66,9 +62,6 @@
 
     @Test
     fun constructor_requiredParameters_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructEntryWithRequiredParams()
 
         assertNotNull(entry)
@@ -82,9 +75,6 @@
 
     @Test
     fun constructor_allParameters_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructEntryWithAllParams()
 
         assertNotNull(entry)
@@ -93,9 +83,6 @@
 
     @Test
     fun constructor_emptyAccountName_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         Assert.assertThrows(
             "Expected empty account name to throw NPE",
             IllegalArgumentException::class.java
@@ -107,13 +94,15 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_requiredParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val originalEntry = constructEntryWithRequiredParams()
 
-        val entry = fromSlice(CreateEntry.toSlice(originalEntry))
+        val slice = CreateEntry.toSlice(originalEntry)
+
+        assertNotNull(slice)
+
+        val entry = fromSlice(CreateEntry.toSlice(originalEntry)!!)
 
         assertNotNull(entry)
         entry?.let {
@@ -122,13 +111,15 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val originalEntry = constructEntryWithAllParams()
 
-        val entry = fromSlice(CreateEntry.toSlice(originalEntry))
+        val slice = CreateEntry.toSlice(originalEntry)
+
+        assertNotNull(slice)
+
+        val entry = fromSlice(slice!!)
 
         assertNotNull(entry)
         entry?.let {
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
index db921cc..fdee504 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryJavaTest.java
@@ -22,13 +22,13 @@
 import static org.junit.Assert.assertThrows;
 
 import android.app.PendingIntent;
+import android.app.slice.Slice;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.R;
 import androidx.credentials.TestUtilsKt;
 import androidx.credentials.provider.BeginGetCredentialOption;
@@ -45,8 +45,8 @@
 import java.time.Instant;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 26)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 public class CustomCredentialEntryJavaTest {
     private static final CharSequence TITLE = "title";
     private static final CharSequence SUBTITLE = "subtitle";
@@ -59,7 +59,7 @@
     private static final boolean IS_AUTO_SELECT_ALLOWED = true;
     private final BeginGetCredentialOption mBeginCredentialOption =
             new BeginGetCustomCredentialOption(
-            "id", "custom", new Bundle());
+            "id", "custom_type", new Bundle());
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private final Intent mIntent = new Intent();
@@ -69,33 +69,22 @@
 
     @Test
     public void build_requiredParameters_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CustomCredentialEntry entry = constructEntryWithRequiredParams();
 
         assertNotNull(entry);
-        assertNotNull(entry.getSlice());
         assertEntryWithRequiredParams(entry);
     }
 
     @Test
     public void build_allParameters_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CustomCredentialEntry entry = constructEntryWithAllParams();
 
         assertNotNull(entry);
-        assertNotNull(entry.getSlice());
         assertEntryWithAllParams(entry);
     }
 
     @Test
     public void build_nullTitle_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null title to throw NPE",
                 NullPointerException.class,
                 () -> new CustomCredentialEntry.Builder(
@@ -105,9 +94,6 @@
 
     @Test
     public void build_nullContext_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null title to throw NPE",
                 NullPointerException.class,
                 () -> new CustomCredentialEntry.Builder(
@@ -117,9 +103,6 @@
 
     @Test
     public void build_nullPendingIntent_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null pending intent to throw NPE",
                 NullPointerException.class,
                 () -> new CustomCredentialEntry.Builder(
@@ -129,9 +112,6 @@
 
     @Test
     public void build_nullBeginOption_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null option to throw NPE",
                 NullPointerException.class,
                 () -> new CustomCredentialEntry.Builder(
@@ -141,9 +121,6 @@
 
     @Test
     public void build_emptyTitle_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected empty title to throw IAE",
                 IllegalArgumentException.class,
                 () -> new CustomCredentialEntry.Builder(
@@ -153,9 +130,6 @@
 
     @Test
     public void build_emptyType_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected empty type to throw NPE",
                 IllegalArgumentException.class,
                 () -> new CustomCredentialEntry.Builder(
@@ -165,9 +139,6 @@
 
     @Test
     public void build_nullIcon_defaultIconSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CustomCredentialEntry entry = constructEntryWithRequiredParams();
 
         assertThat(TestUtilsKt.equals(entry.getIcon(),
@@ -175,28 +146,25 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_requiredParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CustomCredentialEntry originalEntry = constructEntryWithRequiredParams();
 
+        Slice slice = CustomCredentialEntry.toSlice(originalEntry);
         CustomCredentialEntry entry = CustomCredentialEntry.fromSlice(
-                originalEntry.getSlice());
+                slice);
 
         assertNotNull(entry);
         assertEntryWithRequiredParamsFromSlice(entry);
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         CustomCredentialEntry originalEntry = constructEntryWithAllParams();
 
-        CustomCredentialEntry entry = CustomCredentialEntry.fromSlice(
-                originalEntry.getSlice());
+        Slice slice = CustomCredentialEntry.toSlice(originalEntry);
+        CustomCredentialEntry entry = CustomCredentialEntry.fromSlice(slice);
 
         assertNotNull(entry);
         assertEntryWithAllParamsFromSlice(entry);
@@ -261,6 +229,5 @@
         assertThat(IS_AUTO_SELECT_ALLOWED).isEqualTo(entry.isAutoSelectAllowed());
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
         assertThat(mBeginCredentialOption.getType()).isEqualTo(entry.getType());
-        assertThat(mBeginCredentialOption).isEqualTo(entry.getBeginGetCredentialOption());
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
index 8b31911..a1b107b 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/CustomCredentialEntryTest.kt
@@ -21,7 +21,6 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
 import android.os.Bundle
-import androidx.core.os.BuildCompat
 import androidx.credentials.R
 import androidx.credentials.equals
 import androidx.credentials.provider.BeginGetCredentialOption
@@ -39,8 +38,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 @RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 26)
 @SmallTest
 class CustomCredentialEntryTest {
     private val mContext = ApplicationProvider.getApplicationContext<Context>()
@@ -48,46 +47,32 @@
     private val mPendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
         PendingIntent.FLAG_IMMUTABLE)
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun constructor_requiredParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructEntryWithRequiredParams()
 
         assertNotNull(entry)
-        assertNotNull(entry.slice)
         assertEntryWithRequiredParams(entry)
     }
 
     @Test
     fun constructor_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructEntryWithAllParams()
 
         assertNotNull(entry)
-        assertNotNull(entry.slice)
         assertEntryWithAllParams(entry)
     }
 
     @Test
     fun constructor_allParameters_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry: CustomCredentialEntry = constructEntryWithAllParams()
 
         assertNotNull(entry)
-        assertNotNull(entry.slice)
         assertEntryWithAllParams(entry)
     }
 
     @Test
     fun constructor_emptyTitle_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         assertThrows(
             "Expected empty title to throw NPE",
             IllegalArgumentException::class.java
@@ -101,10 +86,8 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun constructor_emptyType_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         assertThrows(
             "Expected empty type to throw NPE",
             IllegalArgumentException::class.java
@@ -117,10 +100,8 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 23)
     fun constructor_nullIcon_defaultIconSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructEntryWithRequiredParams()
 
         assertThat(
@@ -132,13 +113,14 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_requiredParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val originalEntry = constructEntryWithRequiredParams()
 
-        val entry = fromSlice(originalEntry.slice)
+        val slice = CustomCredentialEntry.toSlice(
+            originalEntry)
+        assertNotNull(slice)
+        val entry = fromSlice(slice!!)
 
         assertNotNull(entry)
         if (entry != null) {
@@ -147,13 +129,14 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val originalEntry = constructEntryWithAllParams()
 
-        val entry = fromSlice(originalEntry.slice)
+        val slice = CustomCredentialEntry.toSlice(
+        originalEntry)
+        assertNotNull(slice)
+        val entry = fromSlice(slice!!)
 
         assertNotNull(entry)
         if (entry != null) {
@@ -205,7 +188,6 @@
         assertThat(IS_AUTO_SELECT_ALLOWED).isEqualTo(entry.isAutoSelectAllowed)
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
         assertThat(BEGIN_OPTION.type).isEqualTo(entry.type)
-        assertThat(BEGIN_OPTION).isEqualTo(entry.beginGetCredentialOption)
     }
 
     private fun assertEntryWithRequiredParams(entry: CustomCredentialEntry) {
@@ -219,13 +201,12 @@
         assertThat(TITLE == entry.title)
         assertThat(mPendingIntent).isEqualTo(entry.pendingIntent)
         assertThat(BEGIN_OPTION.type).isEqualTo(entry.type)
-        assertThat(BEGIN_OPTION).isEqualTo(entry.beginGetCredentialOption)
     }
 
     companion object {
         private val TITLE: CharSequence = "title"
         private val BEGIN_OPTION: BeginGetCredentialOption = BeginGetCustomCredentialOption(
-            "id", "type", Bundle())
+            "id", "custom_type", Bundle())
         private val SUBTITLE: CharSequence = "subtitle"
         private const val TYPE = "custom_type"
         private val TYPE_DISPLAY_NAME: CharSequence = "Password"
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
index 8c2fe34..cb08b7c1 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryJavaTest.java
@@ -29,7 +29,6 @@
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.PasswordCredential;
 import androidx.credentials.R;
 import androidx.credentials.TestUtilsKt;
@@ -47,8 +46,8 @@
 import java.util.HashSet;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 26)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 public class PasswordCredentialEntryJavaTest {
     private static final CharSequence USERNAME = "title";
     private static final CharSequence DISPLAYNAME = "subtitle";
@@ -71,35 +70,24 @@
 
     @Test
     public void build_requiredParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PasswordCredentialEntry entry = constructEntryWithRequiredParamsOnly();
 
         assertNotNull(entry);
-        assertNotNull(entry.getSlice());
         assertThat(entry.getType()).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL);
         assertEntryWithRequiredParamsOnly(entry, false);
     }
 
     @Test
     public void build_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PasswordCredentialEntry entry = constructEntryWithAllParams();
 
         assertNotNull(entry);
-        assertNotNull(entry.getSlice());
         assertThat(entry.getType()).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL);
         assertEntryWithAllParams(entry);
     }
 
     @Test
     public void build_nullContext_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null context to throw NPE",
                 NullPointerException.class,
                 () -> new PasswordCredentialEntry.Builder(
@@ -109,9 +97,6 @@
 
     @Test
     public void build_nullUsername_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null username to throw NPE",
                 NullPointerException.class,
                 () -> new PasswordCredentialEntry.Builder(
@@ -121,9 +106,6 @@
 
     @Test
     public void build_nullPendingIntent_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null pending intent to throw NPE",
                 NullPointerException.class,
                 () -> new PasswordCredentialEntry.Builder(
@@ -133,9 +115,6 @@
 
     @Test
     public void build_nullBeginOption_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null option to throw NPE",
                 NullPointerException.class,
                 () -> new PasswordCredentialEntry.Builder(
@@ -145,9 +124,6 @@
 
     @Test
     public void build_emptyUsername_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected empty username to throw IllegalArgumentException",
                 IllegalArgumentException.class,
                 () -> new PasswordCredentialEntry.Builder(
@@ -156,9 +132,6 @@
 
     @Test
     public void build_nullIcon_defaultIconSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PasswordCredentialEntry entry = new PasswordCredentialEntry
                 .Builder(mContext, USERNAME, mPendingIntent, mBeginGetPasswordOption).build();
 
@@ -168,9 +141,6 @@
 
     @Test
     public void build_nullTypeDisplayName_defaultDisplayNameSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PasswordCredentialEntry entry = new PasswordCredentialEntry.Builder(
                         mContext, USERNAME, mPendingIntent, mBeginGetPasswordOption).build();
 
@@ -182,39 +152,30 @@
 
     @Test
     public void build_isAutoSelectAllowedDefault_false() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PasswordCredentialEntry entry = constructEntryWithRequiredParamsOnly();
 
         assertFalse(entry.isAutoSelectAllowed());
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_requiredParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PasswordCredentialEntry originalEntry = constructEntryWithRequiredParamsOnly();
 
-        assertNotNull(originalEntry.getSlice());
         PasswordCredentialEntry entry = PasswordCredentialEntry.fromSlice(
-                originalEntry.getSlice());
+                PasswordCredentialEntry.toSlice(originalEntry));
 
         assertNotNull(entry);
         assertEntryWithRequiredParamsOnly(entry, true);
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PasswordCredentialEntry originalEntry = constructEntryWithAllParams();
 
-        assertNotNull(originalEntry.getSlice());
         PasswordCredentialEntry entry = PasswordCredentialEntry.fromSlice(
-                originalEntry.getSlice());
+                PasswordCredentialEntry.toSlice(originalEntry));
 
         assertNotNull(entry);
         assertEntryWithAllParams(entry);
@@ -246,7 +207,6 @@
         assertThat(USERNAME.equals(entry.getUsername()));
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
         assertThat(mBeginGetPasswordOption.getType()).isEqualTo(entry.getType());
-        assertThat(mBeginGetPasswordOption).isEqualTo(entry.getBeginGetCredentialOption());
     }
 
     private void assertEntryWithAllParams(PasswordCredentialEntry entry) {
@@ -258,6 +218,5 @@
         assertThat(Instant.ofEpochMilli(LAST_USED_TIME)).isEqualTo(entry.getLastUsedTime());
         assertThat(mPendingIntent).isEqualTo(entry.getPendingIntent());
         assertThat(mBeginGetPasswordOption.getType()).isEqualTo(entry.getType());
-        assertThat(mBeginGetPasswordOption).isEqualTo(entry.getBeginGetCredentialOption());
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
index 6c86911..d0f1114 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PasswordCredentialEntryTest.kt
@@ -21,7 +21,6 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
 import android.os.Bundle
-import androidx.core.os.BuildCompat
 import androidx.credentials.PasswordCredential
 import androidx.credentials.R
 import androidx.credentials.equals
@@ -40,8 +39,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 @RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 26)
 @SmallTest
 class PasswordCredentialEntryTest {
     private val mContext = ApplicationProvider.getApplicationContext<Context>()
@@ -53,35 +52,24 @@
 
     @Test
     fun constructor_requiredParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructEntryWithRequiredParamsOnly()
 
         assertNotNull(entry)
-        assertNotNull(entry.slice)
         assertThat(entry.type).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL)
         assertEntryWithRequiredParamsOnly(entry)
     }
 
     @Test
     fun constructor_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructEntryWithAllParams()
 
         assertNotNull(entry)
-        assertNotNull(entry.slice)
         assertThat(entry.type).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL)
         assertEntryWithAllParams(entry)
     }
 
     @Test
     fun constructor_emptyUsername_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         assertThrows(
             "Expected empty username to throw IllegalArgumentException",
             IllegalArgumentException::class.java
@@ -94,9 +82,6 @@
 
     @Test
     fun constructor_nullIcon_defaultIconSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = PasswordCredentialEntry.Builder(
             mContext, USERNAME, mPendingIntent, BEGIN_OPTION).build()
 
@@ -110,9 +95,6 @@
 
     @Test
     fun constructor_nullTypeDisplayName_defaultDisplayNameSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = PasswordCredentialEntry(
             mContext, USERNAME, mPendingIntent, BEGIN_OPTION)
 
@@ -125,9 +107,6 @@
 
     @Test
     fun constructor_isAutoSelectAllowedDefault_false() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructEntryWithRequiredParamsOnly()
         val entry1 = constructEntryWithAllParams()
 
@@ -136,13 +115,15 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val originalEntry = constructEntryWithAllParams()
 
-        val entry = fromSlice(originalEntry.slice)
+        val slice = PasswordCredentialEntry.toSlice(originalEntry)
+
+        assertNotNull(slice)
+
+        val entry = fromSlice(slice!!)
 
         assertNotNull(entry)
         entry?.let {
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
index ffec2b8..05a49e5 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryJavaTest.java
@@ -28,7 +28,6 @@
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.PublicKeyCredential;
 import androidx.credentials.R;
 import androidx.credentials.TestUtilsKt;
@@ -45,8 +44,8 @@
 import java.time.Instant;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 26)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 public class PublicKeyCredentialEntryJavaTest {
     private static final CharSequence USERNAME = "title";
     private static final CharSequence DISPLAYNAME = "subtitle";
@@ -67,35 +66,24 @@
 
     @Test
     public void build_requiredParamsOnly_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PublicKeyCredentialEntry entry = constructWithRequiredParamsOnly();
 
         assertNotNull(entry);
-        assertNotNull(entry.getSlice());
         assertThat(entry.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
         assertEntryWithRequiredParams(entry);
     }
 
     @Test
     public void build_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PublicKeyCredentialEntry entry = constructWithAllParams();
 
         assertNotNull(entry);
-        assertNotNull(entry.getSlice());
         assertThat(entry.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
         assertEntryWithAllParams(entry);
     }
 
     @Test
     public void build_withNullUsername_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null username to throw NPE",
                 NullPointerException.class,
                 () -> new PublicKeyCredentialEntry.Builder(
@@ -105,9 +93,6 @@
 
     @Test
     public void build_withNullBeginOption_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null option to throw NPE",
                 NullPointerException.class,
                 () -> new PublicKeyCredentialEntry.Builder(
@@ -117,9 +102,6 @@
 
     @Test
     public void build_withNullPendingIntent_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null pending intent to throw NPE",
                 NullPointerException.class,
                 () -> new PublicKeyCredentialEntry.Builder(
@@ -129,9 +111,6 @@
 
     @Test
     public void build_withEmptyUsername_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected empty username to throw IllegalArgumentException",
                 IllegalArgumentException.class,
                 () -> new PublicKeyCredentialEntry.Builder(
@@ -140,9 +119,6 @@
 
     @Test
     public void build_withNullIcon_defaultIconSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry
                 .Builder(
                 mContext, USERNAME, mPendingIntent, mBeginOption).build();
@@ -153,9 +129,6 @@
 
     @Test
     public void build_nullTypeDisplayName_defaultDisplayNameSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PublicKeyCredentialEntry entry = new PublicKeyCredentialEntry.Builder(
                         mContext, USERNAME, mPendingIntent, mBeginOption).build();
 
@@ -166,15 +139,12 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         PublicKeyCredentialEntry originalEntry = constructWithAllParams();
-        assertNotNull(originalEntry.getSlice());
 
         PublicKeyCredentialEntry entry = PublicKeyCredentialEntry.fromSlice(
-                originalEntry.getSlice());
+                PublicKeyCredentialEntry.toSlice(originalEntry));
 
         assertNotNull(entry);
         assertEntryWithRequiredParams(entry);
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
index ba4f546..74c9a24 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/PublicKeyCredentialEntryTest.kt
@@ -21,7 +21,6 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
 import android.os.Bundle
-import androidx.core.os.BuildCompat
 import androidx.credentials.PublicKeyCredential
 import androidx.credentials.R
 import androidx.credentials.equals
@@ -39,7 +38,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = 26)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class PublicKeyCredentialEntryTest {
@@ -52,34 +51,23 @@
 
     @Test
     fun constructor_requiredParamsOnly_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructWithRequiredParamsOnly()
 
         assertNotNull(entry)
-        assertNotNull(entry.slice)
         assertThat(entry.type).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
         assertEntryWithRequiredParams(entry)
     }
 
     @Test
     fun constructor_allParams_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = constructWithAllParams()
         assertNotNull(entry)
-        assertNotNull(entry.slice)
         assertThat(entry.type).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
         assertEntryWithAllParams(entry)
     }
 
     @Test
     fun constructor_emptyUsername_throwsIAE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         assertThrows(
             "Expected empty username to throw IllegalArgumentException",
             IllegalArgumentException::class.java
@@ -92,9 +80,6 @@
 
     @Test
     fun constructor_nullIcon_defaultIconSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = PublicKeyCredentialEntry(
             mContext, USERNAME, mPendingIntent, BEGIN_OPTION
         )
@@ -109,9 +94,6 @@
 
     @Test
     fun constructor_nullTypeDisplayName_defaultDisplayNameSet() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = PublicKeyCredentialEntry(
             mContext, USERNAME, mPendingIntent, BEGIN_OPTION
         )
@@ -123,13 +105,13 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val originalEntry = constructWithAllParams()
+        val slice = PublicKeyCredentialEntry.toSlice(originalEntry)
 
-        val entry = fromSlice(originalEntry.slice)
+        assertNotNull(slice)
+        val entry = fromSlice(slice!!)
 
         assertNotNull(entry)
         entry?.let {
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/RemoteEntryJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/RemoteEntryJavaTest.java
index 90be938..a90d2cf 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/RemoteEntryJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/RemoteEntryJavaTest.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 
-import androidx.core.os.BuildCompat;
 import androidx.credentials.provider.RemoteEntry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -37,7 +36,6 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 public class RemoteEntryJavaTest {
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private final Intent mIntent = new Intent();
@@ -47,9 +45,6 @@
 
     @Test
     public void constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         RemoteEntry entry = new RemoteEntry(mPendingIntent);
 
         assertNotNull(entry);
@@ -58,9 +53,6 @@
 
     @Test
     public void build_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         RemoteEntry entry = new RemoteEntry.Builder(mPendingIntent).build();
 
         assertNotNull(entry);
@@ -69,19 +61,14 @@
 
     @Test
     public void constructor_nullPendingIntent_throwsNPE() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         assertThrows("Expected null pending intent to throw NPE",
                 NullPointerException.class,
                 () -> new RemoteEntry(null));
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     public void fromSlice_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return;
-        }
         RemoteEntry originalEntry = new RemoteEntry(mPendingIntent);
 
         RemoteEntry fromSlice = RemoteEntry.fromSlice(RemoteEntry.toSlice(originalEntry));
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/RemoteEntryTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/RemoteEntryTest.kt
index 0278077..ba8e42f 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/RemoteEntryTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/ui/RemoteEntryTest.kt
@@ -18,7 +18,6 @@
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
-import androidx.core.os.BuildCompat
 import androidx.credentials.provider.RemoteEntry
 import androidx.credentials.provider.RemoteEntry.Companion.fromSlice
 import androidx.test.core.app.ApplicationProvider
@@ -32,7 +31,6 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-@SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
 class RemoteEntryTest {
     private val mContext = ApplicationProvider.getApplicationContext<Context>()
     private val mIntent = Intent()
@@ -41,9 +39,6 @@
 
     @Test
     fun constructor_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = RemoteEntry(mPendingIntent)
 
         assertNotNull(entry)
@@ -52,9 +47,6 @@
 
     @Test
     fun build_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val entry = RemoteEntry.Builder(mPendingIntent).build()
 
         assertNotNull(entry)
@@ -62,10 +54,8 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 28)
     fun fromSlice_success() {
-        if (!BuildCompat.isAtLeastU()) {
-            return
-        }
         val originalEntry = RemoteEntry(mPendingIntent)
 
         val fromSlice = fromSlice(RemoteEntry.toSlice(originalEntry))
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
index 11302ef..7b571a4 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFrameworkImpl.kt
@@ -359,7 +359,6 @@
 
             override fun onError(error: android.credentials.ClearCredentialStateException) {
                 Log.i(TAG, "ClearCredentialStateException error returned from framework")
-                // TODO("Covert to the appropriate exception")
                 callback.onError(ClearCredentialUnknownException())
             }
         }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/PrepareGetCredentialResponse.kt b/credentials/credentials/src/main/java/androidx/credentials/PrepareGetCredentialResponse.kt
index f81db73..e4d2572 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/PrepareGetCredentialResponse.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/PrepareGetCredentialResponse.kt
@@ -42,9 +42,9 @@
  * operation; pass this handle to [CredentialManager.getCredential] (Kotlin) /
  * [CredentialManager.getCredentialAsync] (Java) to perform the remaining flows to officially
  * retrieve a credential.
- * @property hasRemoteResultsHandler whether the response has remote results
- * @property hasAuthenticationResultsHandler whether the response has auth results
- * @property credentialTypeHandler whether the response has a credential result handler
+ * @property hasRemoteResultsDelegate whether the response has remote results
+ * @property hasAuthResultsDelegate whether the response has auth results
+ * @property credentialTypeDelegate whether the response has a credential result handler
  * @property isNullHandlesForTest whether to support null handles for test
  * @throws NullPointerException If [pendingGetCredentialHandle] is null at API level >= 34.
  */
@@ -52,9 +52,9 @@
 @SuppressLint("MissingGetterMatchingBuilder")
 class PrepareGetCredentialResponse private constructor(
     val pendingGetCredentialHandle: PendingGetCredentialHandle?,
-    val hasRemoteResultsHandler: HasRemoteResultsHandler?,
-    val hasAuthenticationResultsHandler: HasAuthenticationResultsHandler?,
-    val credentialTypeHandler: HasCredentialResultsHandler?,
+    val hasRemoteResultsDelegate: HasRemoteResultsDelegate?,
+    val hasAuthResultsDelegate: HasAuthenticationResultsDelegate?,
+    val credentialTypeDelegate: HasCredentialResultsDelegate?,
     val isNullHandlesForTest: Boolean,
 ) {
 
@@ -75,8 +75,8 @@
      */
     @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS)
     fun hasCredentialResults(credentialType: String): Boolean {
-        if (credentialTypeHandler != null) {
-            return credentialTypeHandler.invoke(credentialType)
+        if (credentialTypeDelegate != null) {
+            return credentialTypeDelegate.invoke(credentialType)
         }
         return false
     }
@@ -89,8 +89,8 @@
      */
     @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS)
     fun hasAuthenticationResults(): Boolean {
-        if (hasAuthenticationResultsHandler != null) {
-            return hasAuthenticationResultsHandler.invoke()
+        if (hasAuthResultsDelegate != null) {
+            return hasAuthResultsDelegate.invoke()
         }
         return false
     }
@@ -102,8 +102,8 @@
      */
     @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS)
     fun hasRemoteResults(): Boolean {
-        if (hasRemoteResultsHandler != null) {
-            return hasRemoteResultsHandler.invoke()
+        if (hasRemoteResultsDelegate != null) {
+            return hasRemoteResultsDelegate.invoke()
         }
         return false
     }
@@ -131,46 +131,21 @@
     }
 
     /** A builder for [PrepareGetCredentialResponse]. */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     class Builder {
         private var pendingGetCredentialHandle: PendingGetCredentialHandle? = null
-        private var hasRemoteResultsHandler: HasRemoteResultsHandler? = null
-        private var hasAuthenticationResultsHandler: HasAuthenticationResultsHandler? = null
-        private var hasCredentialResultsHandler: HasCredentialResultsHandler? = null
+        private var hasRemoteResultsDelegate: HasRemoteResultsDelegate? = null
+        private var hasAuthResultsDelegate: HasAuthenticationResultsDelegate? = null
+        private var hasCredentialResultsDelegate: HasCredentialResultsDelegate? = null
         private var frameworkResponse: PrepareGetCredentialResponse? = null
-        private var isNullHandlesForTest = false
-
-        /** Sets the credential type handler. */
-        fun setCredentialTypeHandler(handler: HasCredentialResultsHandler): Builder {
-            this.hasCredentialResultsHandler = handler
-            return this
-        }
-
-        /** Sets the has authentication results bit. */
-        fun setHasAuthenticationResultsHandler(handler: HasAuthenticationResultsHandler): Builder {
-            this.hasAuthenticationResultsHandler = handler
-            return this
-        }
-
-        /** Sets the has remote results bit. */
-        fun setHasRemoteResultsHandler(handler: HasRemoteResultsHandler): Builder {
-            this.hasRemoteResultsHandler = handler
-            return this
-        }
-
-        /** Sets enabling null handles for test. */
-        @VisibleForTesting
-        fun setIsNullHandlesForTest(setValue: Boolean): Builder {
-            this.isNullHandlesForTest = setValue
-            return this
-        }
 
         /** Sets the framework response. */
         fun setFrameworkResponse(resp: PrepareGetCredentialResponse?): Builder {
             this.frameworkResponse = resp
             if (resp != null) {
-                this.setCredentialTypeHandler(this::hasCredentialType)
-                this.setHasAuthenticationResultsHandler(this::hasAuthenticationResults)
-                this.setHasRemoteResultsHandler(this::hasRemoteResults)
+                this.hasCredentialResultsDelegate = this::hasCredentialType
+                this.hasAuthResultsDelegate = this::hasAuthenticationResults
+                this.hasRemoteResultsDelegate = this::hasRemoteResults
             }
             return this
         }
@@ -203,17 +178,57 @@
         fun build(): androidx.credentials.PrepareGetCredentialResponse {
             return androidx.credentials.PrepareGetCredentialResponse(
                 pendingGetCredentialHandle,
-                hasRemoteResultsHandler,
-                hasAuthenticationResultsHandler,
-                hasCredentialResultsHandler,
-                isNullHandlesForTest
+                hasRemoteResultsDelegate,
+                hasAuthResultsDelegate,
+                hasCredentialResultsDelegate,
+                false
+            )
+        }
+    }
+
+    /** A builder for [PrepareGetCredentialResponse] for test use only. */
+    @VisibleForTesting
+    class TestBuilder {
+        private var hasRemoteResultsDelegate: HasRemoteResultsDelegate? = null
+        private var hasAuthResultsDelegate: HasAuthenticationResultsDelegate? = null
+        private var hasCredentialResultsDelegate: HasCredentialResultsDelegate? = null
+
+        /** Sets the credential type handler. */
+        fun setCredentialTypeDelegate(handler: HasCredentialResultsDelegate): TestBuilder {
+            this.hasCredentialResultsDelegate = handler
+            return this
+        }
+
+        /** Sets the has authentication results bit. */
+        fun setHasAuthResultsDelegate(handler: HasAuthenticationResultsDelegate): TestBuilder {
+            this.hasAuthResultsDelegate = handler
+            return this
+        }
+
+        /** Sets the has remote results bit. */
+        fun setHasRemoteResultsDelegate(handler: HasRemoteResultsDelegate): TestBuilder {
+            this.hasRemoteResultsDelegate = handler
+            return this
+        }
+
+        /**
+         * Builds a [PrepareGetCredentialResponse].
+         */
+        @SuppressLint("SyntheticAccessor")
+        fun build(): androidx.credentials.PrepareGetCredentialResponse {
+            return androidx.credentials.PrepareGetCredentialResponse(
+                null,
+                hasRemoteResultsDelegate,
+                hasAuthResultsDelegate,
+                hasCredentialResultsDelegate,
+                true
             )
         }
     }
 }
 
-typealias HasCredentialResultsHandler = (String) -> Boolean
+typealias HasCredentialResultsDelegate = (String) -> Boolean
 
-typealias HasAuthenticationResultsHandler = () -> Boolean
+typealias HasAuthenticationResultsDelegate = () -> Boolean
 
-typealias HasRemoteResultsHandler = () -> Boolean
\ No newline at end of file
+typealias HasRemoteResultsDelegate = () -> Boolean
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOption.kt
index 766aac0..c1b9b55 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/BeginGetPublicKeyCredentialOption.kt
@@ -35,7 +35,7 @@
  * [android.service.credentials.CallingAppInfo.getOrigin] is set
  *
  * @throws NullPointerException If [requestJson] is null
- * @throws IllegalArgumentException If [requestJson] is empty
+ * @throws IllegalArgumentException If [requestJson] is empty, or is not a valid JSON
  *
  * Note : Credential providers are not expected to utilize the constructor in this class for any
  * production flow. This constructor must only be used for testing purposes.
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt
index 2bfee68..0b4ff95 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CreateEntry.kt
@@ -21,6 +21,7 @@
 import android.app.slice.SliceSpec
 import android.graphics.drawable.Icon
 import android.net.Uri
+import android.os.Build
 import android.os.Bundle
 import android.util.Log
 import androidx.annotation.RequiresApi
@@ -41,7 +42,7 @@
  *
  * @throws IllegalArgumentException If [accountName] is empty
  */
-@RequiresApi(28)
+@RequiresApi(26)
 class CreateEntry internal constructor(
     val accountName: CharSequence,
     val pendingIntent: PendingIntent,
@@ -256,36 +257,8 @@
         }
     }
 
-    internal companion object {
-        private const val TAG = "CreateEntry"
-        private const val DESCRIPTION_MAX_CHAR_LIMIT = 300
-
-        internal const val TYPE_TOTAL_CREDENTIAL = "TOTAL_CREDENTIAL_COUNT_TYPE"
-
-        private const val SLICE_HINT_ACCOUNT_NAME =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME"
-
-        private const val SLICE_HINT_NOTE =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_NOTE"
-
-        private const val SLICE_HINT_ICON =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON"
-
-        private const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION"
-
-        private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
-
-        private const val SLICE_HINT_PENDING_INTENT =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT"
-
-        private const val SLICE_HINT_AUTO_SELECT_ALLOWED =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_SELECT_ALLOWED"
-
-        private const val AUTO_SELECT_TRUE_STRING = "true"
-
-        private const val AUTO_SELECT_FALSE_STRING = "false"
+    @RequiresApi(28)
+    private object Api28Impl {
 
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @JvmStatic
@@ -298,9 +271,8 @@
             val lastUsedTime = createEntry.lastUsedTime
             val credentialCountInformationMap = createEntry.credentialCountInformationMap
             val pendingIntent = createEntry.pendingIntent
-
-            // TODO("Use the right type and revision")
-            val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1))
+            val sliceBuilder = Slice.Builder(Uri.EMPTY,
+                SliceSpec(SLICE_SPEC_TYPE, REVISION_ID))
 
             val autoSelectAllowed = if (createEntry.isAutoSelectAllowed) {
                 AUTO_SELECT_TRUE_STRING
@@ -356,17 +328,10 @@
             return sliceBuilder.build()
         }
 
-        /**
-         * Returns an instance of [CreateEntry] derived from a [Slice] object.
-         *
-         * @param slice the [Slice] object constructed through [toSlice]
-         */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
-        @RequiresApi(28)
         @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
         @JvmStatic
         fun fromSlice(slice: Slice): CreateEntry? {
-            // TODO("Put the right spec and version value")
             var accountName: CharSequence? = null
             var icon: Icon? = null
             var pendingIntent: PendingIntent? = null
@@ -443,4 +408,67 @@
             return bundle
         }
     }
+
+    internal companion object {
+        private const val TAG = "CreateEntry"
+        private const val DESCRIPTION_MAX_CHAR_LIMIT = 300
+
+        internal const val TYPE_TOTAL_CREDENTIAL = "TOTAL_CREDENTIAL_COUNT_TYPE"
+
+        private const val SLICE_HINT_ACCOUNT_NAME =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME"
+
+        private const val SLICE_HINT_NOTE =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_NOTE"
+
+        private const val SLICE_HINT_ICON =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON"
+
+        private const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION"
+
+        private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+
+        private const val SLICE_HINT_PENDING_INTENT =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT"
+
+        private const val SLICE_HINT_AUTO_SELECT_ALLOWED =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_SELECT_ALLOWED"
+
+        private const val AUTO_SELECT_TRUE_STRING = "true"
+
+        private const val AUTO_SELECT_FALSE_STRING = "false"
+
+        private const val SLICE_SPEC_TYPE = "CreateEntry"
+
+        private const val REVISION_ID = 1
+
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmStatic
+        fun toSlice(
+            createEntry: CreateEntry
+        ): Slice? {
+            if (Build.VERSION.SDK_INT >= 28) {
+                return Api28Impl.toSlice(createEntry)
+            }
+            return null
+        }
+
+        /**
+         * Returns an instance of [CreateEntry] derived from a [Slice] object.
+         *
+         * @param slice the [Slice] object constructed through [toSlice]
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmStatic
+        fun fromSlice(
+            slice: Slice
+        ): CreateEntry? {
+            if (Build.VERSION.SDK_INT >= 28) {
+                return Api28Impl.fromSlice(slice)
+            }
+            return null
+        }
+    }
 }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt
index c7a4878..c02bf01 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialEntry.kt
@@ -29,13 +29,11 @@
 abstract class CredentialEntry internal constructor(
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     open val type: String,
-    val beginGetCredentialOption: BeginGetCredentialOption,
-    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    val slice: Slice
+    val beginGetCredentialOption: BeginGetCredentialOption
 ) {
     internal companion object {
         @JvmStatic
-        @RequiresApi(34)
+        @RequiresApi(28)
         internal fun createFrom(slice: Slice): CredentialEntry? {
             return try {
                 when (slice.spec?.type) {
@@ -49,5 +47,16 @@
                 CustomCredentialEntry.fromSlice(slice)
             }
         }
+
+        @JvmStatic
+        @RequiresApi(28)
+        internal fun toSlice(entry: CredentialEntry): Slice? {
+            when (entry) {
+                is PasswordCredentialEntry -> return PasswordCredentialEntry.toSlice(entry)
+                is PublicKeyCredentialEntry -> return PublicKeyCredentialEntry.toSlice(entry)
+                is CustomCredentialEntry -> return CustomCredentialEntry.toSlice(entry)
+            }
+            return null
+        }
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialProviderService.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialProviderService.kt
index 7ba654f..24c8271 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialProviderService.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CredentialProviderService.kt
@@ -26,6 +26,8 @@
 import android.service.credentials.CredentialEntry
 import android.service.credentials.CredentialProviderService
 import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
 import androidx.credentials.exceptions.ClearCredentialException
 import androidx.credentials.exceptions.CreateCredentialException
 import androidx.credentials.provider.utils.BeginCreateCredentialUtil
@@ -88,6 +90,30 @@
 @RequiresApi(34)
 abstract class CredentialProviderService : CredentialProviderService() {
 
+    @set:VisibleForTesting
+    @get:VisibleForTesting
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY)
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    public var isTestMode = false
+
+    @set:VisibleForTesting
+    @get:VisibleForTesting
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY)
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    public var lastCreateRequest: BeginCreateCredentialRequest? = null
+
+    @set:VisibleForTesting
+    @get:VisibleForTesting
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY)
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    public var lastGetRequest: BeginGetCredentialRequest? = null
+
+    @set:VisibleForTesting
+    @get:VisibleForTesting
+    @set:RestrictTo(RestrictTo.Scope.LIBRARY)
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    public var lastClearRequest: ProviderClearCredentialStateRequest? = null
+
     final override fun onBeginGetCredential(
         request: android.service.credentials.BeginGetCredentialRequest,
         cancellationSignal: CancellationSignal,
@@ -109,6 +135,9 @@
                 callback.onError(GetCredentialException(error.type, error.message))
             }
         }
+        if (isTestMode) {
+            lastGetRequest = structuredRequest
+        }
         this.onBeginGetCredentialRequest(structuredRequest, cancellationSignal, outcome)
     }
 
@@ -136,10 +165,11 @@
                 )
             }
         }
-        onBeginCreateCredentialRequest(
-            BeginCreateCredentialUtil.convertToJetpackRequest(request),
-            cancellationSignal, outcome
-        )
+        val jetpackRequest = BeginCreateCredentialUtil.convertToJetpackRequest(request)
+        if (isTestMode) {
+            lastCreateRequest = jetpackRequest
+        }
+        onBeginCreateCredentialRequest(jetpackRequest, cancellationSignal, outcome)
     }
 
     final override fun onClearCredentialState(
@@ -157,8 +187,11 @@
                 callback.onError(ClearCredentialStateException(error.type, error.message))
             }
         }
-        onClearCredentialStateRequest(ClearCredentialUtil.convertToJetpackRequest(request),
-            cancellationSignal, outcome)
+        val jetpackRequest = ClearCredentialUtil.convertToJetpackRequest(request)
+        if (isTestMode) {
+            lastClearRequest = jetpackRequest
+        }
+        onClearCredentialStateRequest(jetpackRequest, cancellationSignal, outcome)
     }
 
     /**
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
index bbcc901..a8a5951 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/CustomCredentialEntry.kt
@@ -22,6 +22,7 @@
 import android.content.Context
 import android.graphics.drawable.Icon
 import android.net.Uri
+import android.os.Build
 import android.os.Bundle
 import android.util.Log
 import androidx.annotation.RequiresApi
@@ -32,7 +33,7 @@
 import java.util.Collections
 
 /**
- * Custom credential entry for a custom credential tyoe that is displayed on the account
+ * Custom credential entry for a custom credential type that is displayed on the account
  * selector UI.
  *
  * Each entry corresponds to an account that can provide a credential.
@@ -57,7 +58,7 @@
  * to true does not guarantee this behavior. The developer must also set this
  * to true, and the framework must determine that only one entry is present
  */
-@RequiresApi(28)
+@RequiresApi(26)
 class CustomCredentialEntry internal constructor(
     override val type: String,
     val title: CharSequence,
@@ -73,18 +74,7 @@
     private val isDefaultIcon: Boolean = false
 ) : CredentialEntry(
     type,
-    beginGetCredentialOption,
-    toSlice(
-        type,
-        title,
-        subtitle,
-        pendingIntent,
-        typeDisplayName,
-        lastUsedTime,
-        icon,
-        isAutoSelectAllowed,
-        beginGetCredentialOption
-    )
+    beginGetCredentialOption
 ) {
     init {
         require(type.isNotEmpty()) { "type must not be empty" }
@@ -99,7 +89,7 @@
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
      * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
      * system to attach the final request
-     * @param beginGetCredentialOption the option from the orginial [BeginGetCredentialResponse],
+     * @param beginGetCredentialOption the option from the original [BeginGetCredentialResponse],
      * for which this credential entry is being added
      * @param subtitle the subTitle shown with this entry on the selector UI
      * @param lastUsedTime the last used time the credential underlying this entry was
@@ -137,56 +127,23 @@
         beginGetCredentialOption
     )
 
-    internal companion object {
-        private const val TAG = "CredentialEntry"
-
-        private const val SLICE_HINT_TYPE_DISPLAY_NAME =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
-
-        private const val SLICE_HINT_TITLE =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
-
-        private const val SLICE_HINT_SUBTITLE =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
-
-        private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
-
-        private const val SLICE_HINT_ICON =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
-
-        private const val SLICE_HINT_PENDING_INTENT =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
-
-        private const val SLICE_HINT_AUTO_ALLOWED =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
-
-        private const val SLICE_HINT_OPTION_ID =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_OPTION_ID"
-
-        private const val SLICE_HINT_AUTO_SELECT_FROM_OPTION =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_SELECT_FROM_OPTION"
-
-        private const val SLICE_HINT_DEFAULT_ICON_RES_ID =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEFAULT_ICON_RES_ID"
-
-        private const val AUTO_SELECT_TRUE_STRING = "true"
-
-        private const val AUTO_SELECT_FALSE_STRING = "false"
-
+    @RequiresApi(28)
+    private object Api28Impl {
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
         @JvmStatic
-        internal fun toSlice(
-            type: String,
-            title: CharSequence,
-            subtitle: CharSequence?,
-            pendingIntent: PendingIntent,
-            typeDisplayName: CharSequence?,
-            lastUsedTime: Instant?,
-            icon: Icon,
-            isAutoSelectAllowed: Boolean?,
-            beginGetCredentialOption: BeginGetCredentialOption
+        fun toSlice(
+            entry: CustomCredentialEntry
         ): Slice {
-            // TODO("Put the right revision value")
+            val type = entry.type
+            val title = entry.title
+            val subtitle = entry.subtitle
+            val pendingIntent = entry.pendingIntent
+            val typeDisplayName = entry.typeDisplayName
+            val lastUsedTime = entry.lastUsedTime
+            val icon = entry.icon
+            val isAutoSelectAllowed = entry.isAutoSelectAllowed
+            val beginGetCredentialOption = entry.beginGetCredentialOption
+
             val autoSelectAllowed = if (isAutoSelectAllowed == true) {
                 AUTO_SELECT_TRUE_STRING
             } else {
@@ -194,7 +151,7 @@
             }
             val sliceBuilder = Slice.Builder(
                 Uri.EMPTY, SliceSpec(
-                    type, 1
+                    type, REVISION_ID
                 )
             )
                 .addText(
@@ -335,6 +292,73 @@
         }
     }
 
+    internal companion object {
+        private const val TAG = "CredentialEntry"
+
+        private const val SLICE_HINT_TYPE_DISPLAY_NAME =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
+
+        private const val SLICE_HINT_TITLE =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
+
+        private const val SLICE_HINT_SUBTITLE =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
+
+        private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+
+        private const val SLICE_HINT_ICON =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
+
+        private const val SLICE_HINT_PENDING_INTENT =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
+
+        private const val SLICE_HINT_AUTO_ALLOWED =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
+
+        private const val SLICE_HINT_OPTION_ID =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_OPTION_ID"
+
+        private const val SLICE_HINT_AUTO_SELECT_FROM_OPTION =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_SELECT_FROM_OPTION"
+
+        private const val SLICE_HINT_DEFAULT_ICON_RES_ID =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEFAULT_ICON_RES_ID"
+
+        private const val AUTO_SELECT_TRUE_STRING = "true"
+
+        private const val AUTO_SELECT_FALSE_STRING = "false"
+
+        private const val REVISION_ID = 1
+
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmStatic
+        fun toSlice(
+            entry: CustomCredentialEntry
+        ): Slice? {
+            if (Build.VERSION.SDK_INT >= 28) {
+                return Api28Impl.toSlice(entry)
+            }
+            return null
+        }
+
+        /**
+         * Returns an instance of [CustomCredentialEntry] derived from a [Slice] object.
+         *
+         * @param slice the [Slice] object constructed through [toSlice]
+         *
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests
+        @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
+        @JvmStatic
+        fun fromSlice(slice: Slice): CustomCredentialEntry? {
+            if (Build.VERSION.SDK_INT >= 28) {
+                return Api28Impl.fromSlice(slice)
+            }
+            return null
+        }
+    }
+
     /**
      * Builder for [CustomCredentialEntry]
      *
@@ -346,7 +370,7 @@
      * @param pendingIntent the [PendingIntent] that will get invoked when the user selects this
      * entry, must be created with flag [PendingIntent.FLAG_MUTABLE] to allow the Android
      * system to attach the final request
-     * @param beginGetCredentialOption the option from the orginial [BeginGetCredentialResponse],
+     * @param beginGetCredentialOption the option from the original [BeginGetCredentialResponse],
      * for which this credential entry is being added
      *
      * @throws NullPointerException If [context], [type], [title], [pendingIntent], or
@@ -407,7 +431,7 @@
 
         /** Builds an instance of [CustomCredentialEntry] */
         fun build(): CustomCredentialEntry {
-            if (icon == null) {
+            if (icon == null && Build.VERSION.SDK_INT >= 23) {
                 icon = Icon.createWithResource(context, R.drawable.ic_other_sign_in)
             }
             return CustomCredentialEntry(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
index 013d3c3..1dc6f2a 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PasswordCredentialEntry.kt
@@ -23,6 +23,7 @@
 import android.content.Context
 import android.graphics.drawable.Icon
 import android.net.Uri
+import android.os.Build
 import android.os.Bundle
 import android.util.Log
 import androidx.annotation.RequiresApi
@@ -30,6 +31,7 @@
 import androidx.credentials.CredentialOption
 import androidx.credentials.PasswordCredential
 import androidx.credentials.R
+import androidx.credentials.provider.PasswordCredentialEntry.Companion.toSlice
 import java.time.Instant
 import java.util.Collections
 
@@ -64,7 +66,7 @@
  *
  * @see CustomCredentialEntry
  */
-@RequiresApi(28)
+@RequiresApi(26)
 class PasswordCredentialEntry internal constructor(
     val username: CharSequence,
     val displayName: CharSequence?,
@@ -78,18 +80,7 @@
     private val isDefaultIcon: Boolean = false
 ) : CredentialEntry(
     PasswordCredential.TYPE_PASSWORD_CREDENTIAL,
-    beginGetPasswordOption,
-    toSlice(
-        PasswordCredential.TYPE_PASSWORD_CREDENTIAL,
-        username,
-        displayName,
-        pendingIntent,
-        typeDisplayName,
-        lastUsedTime,
-        icon,
-        isAutoSelectAllowed,
-        beginGetPasswordOption
-    )
+    beginGetPasswordOption
 ) {
     init {
         require(username.isNotEmpty()) { "username must not be empty" }
@@ -142,56 +133,23 @@
         beginGetPasswordOption,
     )
 
-    internal companion object {
-        private const val TAG = "PasswordCredentialEntry"
-
-        private const val SLICE_HINT_TYPE_DISPLAY_NAME =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
-
-        private const val SLICE_HINT_TITLE =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
-
-        private const val SLICE_HINT_SUBTITLE =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
-
-        private const val SLICE_HINT_DEFAULT_ICON_RES_ID =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEFAULT_ICON_RES_ID"
-
-        private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
-
-        private const val SLICE_HINT_ICON =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
-
-        private const val SLICE_HINT_PENDING_INTENT =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
-
-        private const val SLICE_HINT_OPTION_ID =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_OPTION_ID"
-
-        private const val SLICE_HINT_AUTO_ALLOWED =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
-
-        private const val SLICE_HINT_AUTO_SELECT_FROM_OPTION =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_SELECT_FROM_OPTION"
-
-        private const val AUTO_SELECT_TRUE_STRING = "true"
-
-        private const val AUTO_SELECT_FALSE_STRING = "false"
-
+    @RequiresApi(28)
+    private object Api28Impl {
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
         @JvmStatic
-        internal fun toSlice(
-            type: String,
-            title: CharSequence,
-            subTitle: CharSequence?,
-            pendingIntent: PendingIntent,
-            typeDisplayName: CharSequence?,
-            lastUsedTime: Instant?,
-            icon: Icon,
-            isAutoSelectAllowed: Boolean,
-            beginGetPasswordCredentialOption: BeginGetPasswordOption
+        fun toSlice(
+            entry: PasswordCredentialEntry
         ): Slice {
-            // TODO("Put the right revision value")
+            val type = entry.type
+            val title = entry.username
+            val subtitle = entry.displayName
+            val pendingIntent = entry.pendingIntent
+            val typeDisplayName = entry.typeDisplayName
+            val lastUsedTime = entry.lastUsedTime
+            val icon = entry.icon
+            val isAutoSelectAllowed = entry.isAutoSelectAllowed
+            val beginGetPasswordCredentialOption = entry.beginGetCredentialOption
+
             val autoSelectAllowed = if (isAutoSelectAllowed) {
                 AUTO_SELECT_TRUE_STRING
             } else {
@@ -199,7 +157,7 @@
             }
             val sliceBuilder = Slice.Builder(
                 Uri.EMPTY, SliceSpec(
-                    type, 1
+                    type, REVISION_ID
                 )
             )
                 .addText(
@@ -211,7 +169,7 @@
                     listOf(SLICE_HINT_TITLE)
                 )
                 .addText(
-                    subTitle, /*subType=*/null,
+                    subtitle, /*subType=*/null,
                     listOf(SLICE_HINT_SUBTITLE)
                 )
                 .addText(
@@ -336,6 +294,68 @@
         }
     }
 
+    internal companion object {
+        private const val TAG = "PasswordCredentialEntry"
+
+        private const val SLICE_HINT_TYPE_DISPLAY_NAME =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
+
+        private const val SLICE_HINT_TITLE =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
+
+        private const val SLICE_HINT_SUBTITLE =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
+
+        private const val SLICE_HINT_DEFAULT_ICON_RES_ID =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEFAULT_ICON_RES_ID"
+
+        private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+
+        private const val SLICE_HINT_ICON =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
+
+        private const val SLICE_HINT_PENDING_INTENT =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
+
+        private const val SLICE_HINT_OPTION_ID =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_OPTION_ID"
+
+        private const val SLICE_HINT_AUTO_ALLOWED =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
+
+        private const val SLICE_HINT_AUTO_SELECT_FROM_OPTION =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_SELECT_FROM_OPTION"
+
+        private const val AUTO_SELECT_TRUE_STRING = "true"
+
+        private const val AUTO_SELECT_FALSE_STRING = "false"
+
+        private const val REVISION_ID = 1
+
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmStatic
+        fun toSlice(
+            entry: PasswordCredentialEntry
+        ): Slice? {
+            if (Build.VERSION.SDK_INT >= 28) {
+                return Api28Impl.toSlice(entry)
+            }
+            return null
+        }
+
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmStatic
+        fun fromSlice(
+            slice: Slice
+        ): PasswordCredentialEntry? {
+            if (Build.VERSION.SDK_INT >= 28) {
+                return Api28Impl.fromSlice(slice)
+            }
+            return null
+        }
+    }
+
     /**
      * Builder for [PasswordCredentialEntry]
      *
@@ -397,7 +417,7 @@
 
         /** Builds an instance of [PasswordCredentialEntry] */
         fun build(): PasswordCredentialEntry {
-            if (icon == null) {
+            if (icon == null && Build.VERSION.SDK_INT >= 23) {
                 icon = Icon.createWithResource(context, R.drawable.ic_password)
             }
             val typeDisplayName = context.getString(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
index 40d5f2a..7f6f45e 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/PublicKeyCredentialEntry.kt
@@ -23,6 +23,7 @@
 import android.content.Context
 import android.graphics.drawable.Icon
 import android.net.Uri
+import android.os.Build
 import android.os.Bundle
 import android.util.Log
 import androidx.annotation.RequiresApi
@@ -30,6 +31,7 @@
 import androidx.credentials.CredentialOption
 import androidx.credentials.PublicKeyCredential
 import androidx.credentials.R
+import androidx.credentials.provider.PublicKeyCredentialEntry.Companion.toSlice
 import java.time.Instant
 import java.util.Collections
 
@@ -62,7 +64,7 @@
  *
  * @throws IllegalArgumentException if [username] is empty
  */
-@RequiresApi(28)
+@RequiresApi(26)
 class PublicKeyCredentialEntry internal constructor(
     val username: CharSequence,
     val displayName: CharSequence?,
@@ -76,18 +78,7 @@
     private val isDefaultIcon: Boolean = false
 ) : CredentialEntry(
     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-    beginGetPublicKeyCredentialOption,
-    toSlice(
-        PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-        username,
-        displayName,
-        pendingIntent,
-        typeDisplayName,
-        lastUsedTime,
-        icon,
-        isAutoSelectAllowed,
-        beginGetPublicKeyCredentialOption
-    )
+    beginGetPublicKeyCredentialOption
 ) {
 
     init {
@@ -142,58 +133,23 @@
         beginGetPublicKeyCredentialOption
     )
 
-    internal companion object {
-        private const val TAG = "PublicKeyCredEntry"
-
-        private const val SLICE_HINT_TYPE_DISPLAY_NAME =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
-
-        private const val SLICE_HINT_TITLE =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
-
-        private const val SLICE_HINT_SUBTITLE =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
-
-        private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
-
-        private const val SLICE_HINT_ICON =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
-
-        private const val SLICE_HINT_PENDING_INTENT =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
-
-        private const val SLICE_HINT_AUTO_ALLOWED =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
-
-        private const val SLICE_HINT_OPTION_ID =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_OPTION_ID"
-
-        private const val SLICE_HINT_AUTO_SELECT_FROM_OPTION =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_SELECT_FROM_OPTION"
-
-        private const val SLICE_HINT_DEFAULT_ICON_RES_ID =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEFAULT_ICON_RES_ID"
-
-        private const val AUTO_SELECT_TRUE_STRING = "true"
-
-        private const val AUTO_SELECT_FALSE_STRING = "false"
-
+    @RequiresApi(28)
+    private object Api28Impl {
         @RestrictTo(RestrictTo.Scope.LIBRARY)
-        @RequiresApi(28)
         @JvmStatic
         fun toSlice(
-            type: String,
-            title: CharSequence,
-            subTitle: CharSequence?,
-            pendingIntent: PendingIntent,
-            typeDisplayName: CharSequence?,
-            lastUsedTime: Instant?,
-            icon: Icon,
-            isAutoSelectAllowed: Boolean,
-            beginGetPublicKeyCredentialOption: BeginGetPublicKeyCredentialOption
+            entry: PublicKeyCredentialEntry
         ): Slice {
-            // TODO("Put the right revision value")
+            val type = entry.type
+            val title = entry.username
+            val subTitle = entry.displayName
+            val pendingIntent = entry.pendingIntent
+            val typeDisplayName = entry.typeDisplayName
+            val lastUsedTime = entry.lastUsedTime
+            val icon = entry.icon
+            val isAutoSelectAllowed = entry.isAutoSelectAllowed
+            val beginGetPublicKeyCredentialOption = entry.beginGetCredentialOption
+
             val autoSelectAllowed = if (isAutoSelectAllowed) {
                 AUTO_SELECT_TRUE_STRING
             } else {
@@ -201,7 +157,7 @@
             }
             val sliceBuilder = Slice.Builder(
                 Uri.EMPTY, SliceSpec(
-                    type, 1
+                    type, REVISION_ID
                 )
             )
                 .addText(
@@ -273,13 +229,12 @@
          * @param slice the [Slice] object constructed through [toSlice]
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
-        @RequiresApi(28)
         @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
         @JvmStatic
         fun fromSlice(slice: Slice): PublicKeyCredentialEntry? {
             var typeDisplayName: CharSequence? = null
             var title: CharSequence? = null
-            var subTitle: CharSequence? = null
+            var subtitle: CharSequence? = null
             var icon: Icon? = null
             var pendingIntent: PendingIntent? = null
             var lastUsedTime: Instant? = null
@@ -294,7 +249,7 @@
                 } else if (it.hasHint(SLICE_HINT_TITLE)) {
                     title = it.text
                 } else if (it.hasHint(SLICE_HINT_SUBTITLE)) {
-                    subTitle = it.text
+                    subtitle = it.text
                 } else if (it.hasHint(SLICE_HINT_ICON)) {
                     icon = it.icon
                 } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
@@ -318,7 +273,7 @@
             return try {
                 PublicKeyCredentialEntry(
                     title!!,
-                    subTitle,
+                    subtitle,
                     typeDisplayName!!,
                     pendingIntent!!,
                     icon!!,
@@ -338,6 +293,72 @@
         }
     }
 
+    internal companion object {
+        private const val TAG = "PublicKeyCredEntry"
+
+        private const val SLICE_HINT_TYPE_DISPLAY_NAME =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
+
+        private const val SLICE_HINT_TITLE =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
+
+        private const val SLICE_HINT_SUBTITLE =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
+
+        private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+
+        private const val SLICE_HINT_ICON =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
+
+        private const val SLICE_HINT_PENDING_INTENT =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
+
+        private const val SLICE_HINT_AUTO_ALLOWED =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
+
+        private const val SLICE_HINT_OPTION_ID =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_OPTION_ID"
+
+        private const val SLICE_HINT_AUTO_SELECT_FROM_OPTION =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_SELECT_FROM_OPTION"
+
+        private const val SLICE_HINT_DEFAULT_ICON_RES_ID =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_DEFAULT_ICON_RES_ID"
+
+        private const val AUTO_SELECT_TRUE_STRING = "true"
+
+        private const val AUTO_SELECT_FALSE_STRING = "false"
+
+        private const val REVISION_ID = 1
+
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmStatic
+        fun toSlice(
+            entry: PublicKeyCredentialEntry
+        ): Slice? {
+            if (Build.VERSION.SDK_INT >= 28) {
+                return Api28Impl.toSlice(entry)
+            }
+            return null
+        }
+
+        /**
+         * Returns an instance of [CustomCredentialEntry] derived from a [Slice] object.
+         *
+         * @param slice the [Slice] object constructed through [toSlice]
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
+        @JvmStatic
+        fun fromSlice(slice: Slice): PublicKeyCredentialEntry? {
+            if (Build.VERSION.SDK_INT >= 28) {
+                return Api28Impl.fromSlice(slice)
+            }
+            return null
+        }
+    }
+
     /**
      * Builder for [PublicKeyCredentialEntry]
      */
@@ -386,7 +407,7 @@
 
         /** Builds an instance of [PublicKeyCredentialEntry] */
         fun build(): PublicKeyCredentialEntry {
-            if (icon == null) {
+            if (icon == null && Build.VERSION.SDK_INT >= 23) {
                 icon = Icon.createWithResource(context, R.drawable.ic_passkey)
             }
             val typeDisplayName = context.getString(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/RemoteEntry.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/RemoteEntry.kt
index dae3848..4ee6202 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/RemoteEntry.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/RemoteEntry.kt
@@ -75,6 +75,10 @@
         private const val SLICE_HINT_PENDING_INTENT =
             "androidx.credentials.provider.remoteEntry.SLICE_HINT_PENDING_INTENT"
 
+        private const val SLICE_SPEC_TYPE = "RemoteEntry"
+
+        private const val REVISION_ID = 1
+
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @RequiresApi(28)
         @JvmStatic
@@ -82,8 +86,7 @@
             remoteEntry: RemoteEntry
         ): Slice {
             val pendingIntent = remoteEntry.pendingIntent
-            // TODO("Put the right spec and version value")
-            val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1))
+            val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec(SLICE_SPEC_TYPE, REVISION_ID))
             sliceBuilder.addAction(
                 pendingIntent,
                 Slice.Builder(sliceBuilder)
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/utils/BeginCreateCredentialUtil.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/utils/BeginCreateCredentialUtil.kt
index dc17de7..6a14add 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/utils/BeginCreateCredentialUtil.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/utils/BeginCreateCredentialUtil.kt
@@ -44,8 +44,10 @@
                     PasswordCredential.TYPE_PASSWORD_CREDENTIAL -> {
                         BeginCreatePasswordCredentialRequest.createFrom(
                             request.data, request.callingAppInfo?.let {
-                                CallingAppInfo(it.packageName,
-                                    it.signingInfo, it.origin)
+                                CallingAppInfo(
+                                    it.packageName,
+                                    it.signingInfo, it.origin
+                                )
                             }
                         )
                     }
@@ -54,8 +56,10 @@
                         BeginCreatePublicKeyCredentialRequest.createFrom(
                             request.data,
                             request.callingAppInfo?.let {
-                                CallingAppInfo(it.packageName,
-                                    it.signingInfo, it.origin)
+                                CallingAppInfo(
+                                    it.packageName,
+                                    it.signingInfo, it.origin
+                                )
                             }
                         )
                     }
@@ -64,8 +68,10 @@
                         BeginCreateCustomCredentialRequest(
                             request.type, request.data,
                             request.callingAppInfo?.let {
-                                CallingAppInfo(it.packageName,
-                                    it.signingInfo, it.origin)
+                                CallingAppInfo(
+                                    it.packageName,
+                                    it.signingInfo, it.origin
+                                )
                             }
                         )
                     }
@@ -75,8 +81,10 @@
                     request.type,
                     request.data,
                     request.callingAppInfo?.let {
-                        CallingAppInfo(it.packageName,
-                            it.signingInfo, it.origin)
+                        CallingAppInfo(
+                            it.packageName,
+                            it.signingInfo, it.origin
+                        )
                     }
                 )
             }
@@ -113,11 +121,14 @@
             createEntries: List<CreateEntry>
         ) {
             createEntries.forEach {
-                frameworkBuilder.addCreateEntry(
-                    android.service.credentials.CreateEntry(
-                        CreateEntry.toSlice(it)
+                val entrySlice = CreateEntry.toSlice(it)
+                if (entrySlice != null) {
+                    frameworkBuilder.addCreateEntry(
+                        android.service.credentials.CreateEntry(
+                            entrySlice
+                        )
                     )
-                )
+                }
             }
         }
 
@@ -132,7 +143,8 @@
                 )
             }
             return android.service.credentials.BeginCreateCredentialRequest(
-                request.type, request.candidateQueryData, callingAppInfo)
+                request.type, request.candidateQueryData, callingAppInfo
+            )
         }
 
         fun convertToJetpackResponse(
diff --git a/credentials/credentials/src/main/java/androidx/credentials/provider/utils/BeginGetCredentialUtil.kt b/credentials/credentials/src/main/java/androidx/credentials/provider/utils/BeginGetCredentialUtil.kt
index 8752bcc..71056aa 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/provider/utils/BeginGetCredentialUtil.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/provider/utils/BeginGetCredentialUtil.kt
@@ -110,16 +110,19 @@
             credentialEntries: List<CredentialEntry>
         ) {
             credentialEntries.forEach {
-                builder.addCredentialEntry(
-                    android.service.credentials.CredentialEntry(
-                        android.service.credentials.BeginGetCredentialOption(
-                            it.beginGetCredentialOption.id,
-                            it.type,
-                            Bundle.EMPTY
-                        ),
-                        it.slice
+                val entrySlice = CredentialEntry.toSlice(it)
+                if (entrySlice != null) {
+                    builder.addCredentialEntry(
+                        android.service.credentials.CredentialEntry(
+                            android.service.credentials.BeginGetCredentialOption(
+                                it.beginGetCredentialOption.id,
+                                it.type,
+                                Bundle.EMPTY
+                            ),
+                            entrySlice
+                        )
                     )
-                )
+                }
             }
         }
 
diff --git a/cursoradapter/OWNERS b/cursoradapter/OWNERS
index 347e71c..848a497 100644
--- a/cursoradapter/OWNERS
+++ b/cursoradapter/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 461330
 aelias@google.com
 ryanmentley@google.com
diff --git a/datastore/OWNERS b/datastore/OWNERS
index d34a1de..88e7798e 100644
--- a/datastore/OWNERS
+++ b/datastore/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 907884
+# Bug component: 827162
 spf@google.com
 rohitsat@google.com
 zhiyuanwang@google.com
diff --git a/development/plot-benchmarks/OWNERS b/development/plot-benchmarks/OWNERS
new file mode 100644
index 0000000..2c03293
--- /dev/null
+++ b/development/plot-benchmarks/OWNERS
@@ -0,0 +1,4 @@
+ccraik@google.com
+jgielzak@google.com
+maralb@google.com
+rahulrav@google.com
diff --git a/development/plot-benchmarks/README.md b/development/plot-benchmarks/README.md
index 4164f69..defac2d 100644
--- a/development/plot-benchmarks/README.md
+++ b/development/plot-benchmarks/README.md
@@ -3,6 +3,14 @@
 This tool helps plot benchmark results, and compare them with past results. <br/>
 Just drag and drop benchmark results `json` files, to visualize & compare data.
 
+<br/>
+
+![Example 1](./docs/example_plot.png)
+
+<br/>
+
+![Example Comparison](./docs/example_comparison.png)
+
 ## Setup
 
 ### Install FNM
@@ -16,17 +24,21 @@
 Using Node v18.16.0
 ```
 
+Note: `fnm use` might prompt you to install the version of Node.js being [used](.nvmrc).
+
 ### Install Dependencies
 
 Now, you are ready to install all the dependencies.
 
 ```bash
+# Installs the necessary dependencies.
 npm install
 ```
 
 ### Running Local Dev Server
 
 ```bash
+# Compiles the code, and hot-deploys code to the local dev server.
 npm run-script dev
 
 VITE v4.3.9  ready in 168 ms
@@ -38,4 +50,12 @@
 
 Now navigate to the URL listed in the output.
 
-Have fun !
\ No newline at end of file
+### Usage
+
+* Drag and drop benchmark results (`*.json`) files into the page.
+* Double clicking the items on the legend, toggles the visibility of the dataset in the chart.
+* You can also filter metrics to plot.
+* To compare benchmark runs, just load both results into the page, and select the ones you want to compare.
+
+Have fun !
+
diff --git a/development/plot-benchmarks/docs/example_comparison.png b/development/plot-benchmarks/docs/example_comparison.png
new file mode 100644
index 0000000..20d9142
--- /dev/null
+++ b/development/plot-benchmarks/docs/example_comparison.png
Binary files differ
diff --git a/development/plot-benchmarks/docs/example_plot.png b/development/plot-benchmarks/docs/example_plot.png
new file mode 100644
index 0000000..a91cf93
--- /dev/null
+++ b/development/plot-benchmarks/docs/example_plot.png
Binary files differ
diff --git a/development/plot-benchmarks/package-lock.json b/development/plot-benchmarks/package-lock.json
index 4690ce4..5ee7330 100644
--- a/development/plot-benchmarks/package-lock.json
+++ b/development/plot-benchmarks/package-lock.json
@@ -11,15 +11,28 @@
         "chart.js": "^4.3.0"
       },
       "devDependencies": {
-        "@sveltejs/vite-plugin-svelte": "^2.0.4",
+        "@sveltejs/vite-plugin-svelte": "^2.4.1",
         "@tsconfig/svelte": "^4.0.1",
-        "svelte": "^3.58.0",
-        "svelte-check": "^3.3.1",
+        "svelte": "^4.0.0",
+        "svelte-check": "^3.4.3",
         "tslib": "^2.5.0",
-        "typescript": "^5.0.2",
+        "typescript": "^5.0.0",
         "vite": "^4.3.9"
       }
     },
+    "node_modules/@ampproject/remapping": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+      "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.0",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/@esbuild/android-arm": {
       "version": "0.17.19",
       "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
@@ -372,6 +385,20 @@
         "node": ">=12"
       }
     },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/@jridgewell/resolve-uri": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
@@ -381,6 +408,15 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/@jridgewell/set-array": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
@@ -488,12 +524,30 @@
       "integrity": "sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==",
       "dev": true
     },
+    "node_modules/@types/estree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
+      "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
+      "dev": true
+    },
     "node_modules/@types/pug": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
       "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==",
       "dev": true
     },
+    "node_modules/acorn": {
+      "version": "8.9.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
+      "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/anymatch": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -507,6 +561,24 @@
         "node": ">= 8"
       }
     },
+    "node_modules/aria-query": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.2.1.tgz",
+      "integrity": "sha512-7uFg4b+lETFgdaJyETnILsXgnnzVnkHcgRbwbPwevm5x/LmUlt3MjczMRe1zg824iBgXZNRPTBftNYyRSKLp2g==",
+      "dev": true,
+      "dependencies": {
+        "dequal": "^2.0.3"
+      }
+    },
+    "node_modules/axobject-query": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
+      "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==",
+      "dev": true,
+      "dependencies": {
+        "dequal": "^2.0.3"
+      }
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -600,12 +672,38 @@
         "fsevents": "~2.3.2"
       }
     },
+    "node_modules/code-red": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.3.tgz",
+      "integrity": "sha512-kVwJELqiILQyG5aeuyKFbdsI1fmQy1Cmf7dQ8eGmVuJoaRVdwey7WaMknr2ZFeVSYSKT0rExsa8EGw0aoI/1QQ==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.4.14",
+        "@types/estree": "^1.0.0",
+        "acorn": "^8.8.2",
+        "estree-walker": "^3.0.3",
+        "periscopic": "^3.1.0"
+      }
+    },
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
       "dev": true
     },
+    "node_modules/css-tree": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+      "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+      "dev": true,
+      "dependencies": {
+        "mdn-data": "2.0.30",
+        "source-map-js": "^1.0.1"
+      },
+      "engines": {
+        "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+      }
+    },
     "node_modules/debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -632,6 +730,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/dequal": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+      "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/detect-indent": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
@@ -684,6 +791,15 @@
         "@esbuild/win32-x64": "0.17.19"
       }
     },
+    "node_modules/estree-walker": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "^1.0.0"
+      }
+    },
     "node_modules/fast-glob": {
       "version": "3.2.12",
       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@@ -853,6 +969,15 @@
         "node": ">=0.12.0"
       }
     },
+    "node_modules/is-reference": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.1.tgz",
+      "integrity": "sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "*"
+      }
+    },
     "node_modules/kleur": {
       "version": "4.1.5",
       "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@@ -862,6 +987,12 @@
         "node": ">=6"
       }
     },
+    "node_modules/locate-character": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+      "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+      "dev": true
+    },
     "node_modules/magic-string": {
       "version": "0.30.0",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
@@ -874,6 +1005,12 @@
         "node": ">=12"
       }
     },
+    "node_modules/mdn-data": {
+      "version": "2.0.30",
+      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+      "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+      "dev": true
+    },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -1010,6 +1147,17 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/periscopic": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
+      "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "^1.0.0",
+        "estree-walker": "^3.0.0",
+        "is-reference": "^3.0.0"
+      }
+    },
     "node_modules/picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -1219,12 +1367,27 @@
       }
     },
     "node_modules/svelte": {
-      "version": "3.59.1",
-      "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.1.tgz",
-      "integrity": "sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.0.0.tgz",
+      "integrity": "sha512-+yCYu3AEUu9n91dnQNGIbnVp8EmNQtuF/YImW4+FTXRHard7NMo+yTsWzggPAbj3fUEJ1FBJLkql/jkp6YB5pg==",
       "dev": true,
+      "dependencies": {
+        "@ampproject/remapping": "^2.2.1",
+        "@jridgewell/sourcemap-codec": "^1.4.15",
+        "@jridgewell/trace-mapping": "^0.3.18",
+        "acorn": "^8.8.2",
+        "aria-query": "^5.2.1",
+        "axobject-query": "^3.2.1",
+        "code-red": "^1.0.3",
+        "css-tree": "^2.3.1",
+        "estree-walker": "^3.0.3",
+        "is-reference": "^3.0.1",
+        "locate-character": "^3.0.0",
+        "magic-string": "^0.30.0",
+        "periscopic": "^3.1.0"
+      },
       "engines": {
-        "node": ">= 8"
+        "node": ">=16"
       }
     },
     "node_modules/svelte-check": {
@@ -1436,6 +1599,16 @@
     }
   },
   "dependencies": {
+    "@ampproject/remapping": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+      "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+      "dev": true,
+      "requires": {
+        "@jridgewell/gen-mapping": "^0.3.0",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      }
+    },
     "@esbuild/android-arm": {
       "version": "0.17.19",
       "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
@@ -1590,12 +1763,29 @@
       "dev": true,
       "optional": true
     },
+    "@jridgewell/gen-mapping": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+      "dev": true,
+      "requires": {
+        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      }
+    },
     "@jridgewell/resolve-uri": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
       "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
       "dev": true
     },
+    "@jridgewell/set-array": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+      "dev": true
+    },
     "@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
@@ -1681,12 +1871,24 @@
       "integrity": "sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==",
       "dev": true
     },
+    "@types/estree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
+      "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
+      "dev": true
+    },
     "@types/pug": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
       "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==",
       "dev": true
     },
+    "acorn": {
+      "version": "8.9.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
+      "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
+      "dev": true
+    },
     "anymatch": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -1697,6 +1899,24 @@
         "picomatch": "^2.0.4"
       }
     },
+    "aria-query": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.2.1.tgz",
+      "integrity": "sha512-7uFg4b+lETFgdaJyETnILsXgnnzVnkHcgRbwbPwevm5x/LmUlt3MjczMRe1zg824iBgXZNRPTBftNYyRSKLp2g==",
+      "dev": true,
+      "requires": {
+        "dequal": "^2.0.3"
+      }
+    },
+    "axobject-query": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
+      "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==",
+      "dev": true,
+      "requires": {
+        "dequal": "^2.0.3"
+      }
+    },
     "balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1764,12 +1984,35 @@
         "readdirp": "~3.6.0"
       }
     },
+    "code-red": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.3.tgz",
+      "integrity": "sha512-kVwJELqiILQyG5aeuyKFbdsI1fmQy1Cmf7dQ8eGmVuJoaRVdwey7WaMknr2ZFeVSYSKT0rExsa8EGw0aoI/1QQ==",
+      "dev": true,
+      "requires": {
+        "@jridgewell/sourcemap-codec": "^1.4.14",
+        "@types/estree": "^1.0.0",
+        "acorn": "^8.8.2",
+        "estree-walker": "^3.0.3",
+        "periscopic": "^3.1.0"
+      }
+    },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
       "dev": true
     },
+    "css-tree": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+      "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+      "dev": true,
+      "requires": {
+        "mdn-data": "2.0.30",
+        "source-map-js": "^1.0.1"
+      }
+    },
     "debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1785,6 +2028,12 @@
       "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
       "dev": true
     },
+    "dequal": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+      "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+      "dev": true
+    },
     "detect-indent": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
@@ -1827,6 +2076,15 @@
         "@esbuild/win32-x64": "0.17.19"
       }
     },
+    "estree-walker": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+      "dev": true,
+      "requires": {
+        "@types/estree": "^1.0.0"
+      }
+    },
     "fast-glob": {
       "version": "3.2.12",
       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@@ -1956,12 +2214,27 @@
       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
       "dev": true
     },
+    "is-reference": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.1.tgz",
+      "integrity": "sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==",
+      "dev": true,
+      "requires": {
+        "@types/estree": "*"
+      }
+    },
     "kleur": {
       "version": "4.1.5",
       "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
       "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
       "dev": true
     },
+    "locate-character": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+      "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+      "dev": true
+    },
     "magic-string": {
       "version": "0.30.0",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
@@ -1971,6 +2244,12 @@
         "@jridgewell/sourcemap-codec": "^1.4.13"
       }
     },
+    "mdn-data": {
+      "version": "2.0.30",
+      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+      "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+      "dev": true
+    },
     "merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2065,6 +2344,17 @@
       "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
       "dev": true
     },
+    "periscopic": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
+      "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
+      "dev": true,
+      "requires": {
+        "@types/estree": "^1.0.0",
+        "estree-walker": "^3.0.0",
+        "is-reference": "^3.0.0"
+      }
+    },
     "picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -2191,10 +2481,25 @@
       }
     },
     "svelte": {
-      "version": "3.59.1",
-      "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.1.tgz",
-      "integrity": "sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==",
-      "dev": true
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.0.0.tgz",
+      "integrity": "sha512-+yCYu3AEUu9n91dnQNGIbnVp8EmNQtuF/YImW4+FTXRHard7NMo+yTsWzggPAbj3fUEJ1FBJLkql/jkp6YB5pg==",
+      "dev": true,
+      "requires": {
+        "@ampproject/remapping": "^2.2.1",
+        "@jridgewell/sourcemap-codec": "^1.4.15",
+        "@jridgewell/trace-mapping": "^0.3.18",
+        "acorn": "^8.8.2",
+        "aria-query": "^5.2.1",
+        "axobject-query": "^3.2.1",
+        "code-red": "^1.0.3",
+        "css-tree": "^2.3.1",
+        "estree-walker": "^3.0.3",
+        "is-reference": "^3.0.1",
+        "locate-character": "^3.0.0",
+        "magic-string": "^0.30.0",
+        "periscopic": "^3.1.0"
+      }
     },
     "svelte-check": {
       "version": "3.4.3",
diff --git a/development/plot-benchmarks/package.json b/development/plot-benchmarks/package.json
index 80c366a..28ee731 100644
--- a/development/plot-benchmarks/package.json
+++ b/development/plot-benchmarks/package.json
@@ -10,15 +10,15 @@
     "check": "svelte-check --tsconfig ./tsconfig.json"
   },
   "devDependencies": {
-    "@sveltejs/vite-plugin-svelte": "^2.0.4",
+    "@sveltejs/vite-plugin-svelte": "^2.4.1",
     "@tsconfig/svelte": "^4.0.1",
-    "svelte": "^3.58.0",
-    "svelte-check": "^3.3.1",
+    "svelte": "^4.0.0",
+    "svelte-check": "^3.4.3",
     "tslib": "^2.5.0",
-    "typescript": "^5.0.2",
+    "typescript": "^5.0.0",
     "vite": "^4.3.9"
   },
   "dependencies": {
     "chart.js": "^4.3.0"
   }
-}
+}
\ No newline at end of file
diff --git a/development/plot-benchmarks/src/lib/Chart.svelte b/development/plot-benchmarks/src/lib/Chart.svelte
index f7d382e..257f228 100644
--- a/development/plot-benchmarks/src/lib/Chart.svelte
+++ b/development/plot-benchmarks/src/lib/Chart.svelte
@@ -109,11 +109,12 @@
       </label>
     </div>
     <div class="legend">
-      {#each $legendLabels as label}
+      {#each $legendLabels as label, index}
         <div
           class="item"
-          on:click={onItemClick(label)}
-          on:keyup={onItemClick(label)}
+          on:dblclick={onItemClick(label)}
+          aria-label="legend"
+          role="listitem"
         >
           <span
             class="box"
diff --git a/development/plot-benchmarks/src/regexp.ts b/development/plot-benchmarks/src/regexp.ts
index 588e9e7..b99513d 100644
--- a/development/plot-benchmarks/src/regexp.ts
+++ b/development/plot-benchmarks/src/regexp.ts
@@ -13,5 +13,5 @@
   if (regExp) {
     return (label: string) => regExp.test(label);
   }
-  return (label: string) => label.indexOf(expression) >= 0
+  return (label: string) => label.indexOf(expression) >= 0;
 }
diff --git a/development/plot-benchmarks/src/transforms.ts b/development/plot-benchmarks/src/transforms.ts
index 8e53860..be74e71 100644
--- a/development/plot-benchmarks/src/transforms.ts
+++ b/development/plot-benchmarks/src/transforms.ts
@@ -91,7 +91,7 @@
   return labels;
 }
 
-function histogramPoints(runs: Array<number[]>, buckets: number = 15): Array<Point> {
+function histogramPoints(runs: Array<number[]>, buckets: number = 10): Array<Point> {
   const flattened = runs.flat();
   // Default comparator coerces types to string !
   flattened.sort((a, b) => a - b); // in-place
diff --git a/development/update_studio.sh b/development/update_studio.sh
index 60159c2..5caed58 100755
--- a/development/update_studio.sh
+++ b/development/update_studio.sh
@@ -7,8 +7,8 @@
 
 # Get versions
 echo Getting Studio version and link
-AGP_VERSION=${1:-8.1.0-beta02}
-STUDIO_VERSION_STRING=${2:-"Android Studio Giraffe | 2022.3.1 Beta 2"}
+AGP_VERSION=${1:-8.1.0-beta05}
+STUDIO_VERSION_STRING=${2:-"Android Studio Giraffe | 2022.3.1 Beta 5"}
 STUDIO_IFRAME_LINK=`curl "https://developer.android.com/studio/archive.html" | grep "<iframe " | sed "s/.* src=\"\([^\"]*\)\".*/\1/g"`
 echo iframe link $STUDIO_IFRAME_LINK
 STUDIO_IFRAME_REDIRECT=`curl -s $STUDIO_IFRAME_LINK | grep href | sed 's/.*href="\([^"]*\)".*/\1/g'`
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index dbcecb3..3ddc256 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -8,10 +8,10 @@
 }
 
 dependencies {
-    docs("androidx.activity:activity:1.8.0-alpha05")
-    docs("androidx.activity:activity-compose:1.8.0-alpha05")
-    samples("androidx.activity:activity-compose-samples:1.8.0-alpha05")
-    docs("androidx.activity:activity-ktx:1.8.0-alpha05")
+    docs("androidx.activity:activity:1.8.0-alpha06")
+    docs("androidx.activity:activity-compose:1.8.0-alpha06")
+    samples("androidx.activity:activity-compose-samples:1.8.0-alpha06")
+    docs("androidx.activity:activity-ktx:1.8.0-alpha06")
     docs("androidx.ads:ads-identifier:1.0.0-alpha05")
     docs("androidx.ads:ads-identifier-common:1.0.0-alpha05")
     docs("androidx.ads:ads-identifier-provider:1.0.0-alpha05")
@@ -30,24 +30,24 @@
     docs("androidx.asynclayoutinflater:asynclayoutinflater:1.1.0-alpha01")
     docs("androidx.asynclayoutinflater:asynclayoutinflater-appcompat:1.1.0-alpha01")
     docs("androidx.autofill:autofill:1.3.0-alpha01")
-    docs("androidx.benchmark:benchmark-common:1.2.0-alpha15")
-    docs("androidx.benchmark:benchmark-junit4:1.2.0-alpha15")
-    docs("androidx.benchmark:benchmark-macro:1.2.0-alpha15")
-    docs("androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha15")
+    docs("androidx.benchmark:benchmark-common:1.2.0-alpha16")
+    docs("androidx.benchmark:benchmark-junit4:1.2.0-alpha16")
+    docs("androidx.benchmark:benchmark-macro:1.2.0-alpha16")
+    docs("androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha16")
     docs("androidx.biometric:biometric:1.2.0-alpha05")
     docs("androidx.biometric:biometric-ktx:1.2.0-alpha05")
     samples("androidx.biometric:biometric-ktx-samples:1.2.0-alpha05")
-    docs("androidx.browser:browser:1.6.0-alpha02")
-    docs("androidx.camera:camera-camera2:1.3.0-alpha07")
-    docs("androidx.camera:camera-core:1.3.0-alpha07")
-    docs("androidx.camera:camera-extensions:1.3.0-alpha07")
+    docs("androidx.browser:browser:1.6.0-beta01")
+    docs("androidx.camera:camera-camera2:1.3.0-beta01")
+    docs("androidx.camera:camera-core:1.3.0-beta01")
+    docs("androidx.camera:camera-extensions:1.3.0-beta01")
     stubs(fileTree(dir: "../camera/camera-extensions-stub", include: ["camera-extensions-stub.jar"]))
-    docs("androidx.camera:camera-lifecycle:1.3.0-alpha07")
-    docs("androidx.camera:camera-mlkit-vision:1.3.0-alpha07")
+    docs("androidx.camera:camera-lifecycle:1.3.0-beta01")
+    docs("androidx.camera:camera-mlkit-vision:1.3.0-beta01")
     docs("androidx.camera:camera-previewview:1.1.0-beta02")
-    docs("androidx.camera:camera-video:1.3.0-alpha07")
-    docs("androidx.camera:camera-view:1.3.0-alpha07")
-    docs("androidx.camera:camera-viewfinder:1.3.0-alpha07")
+    docs("androidx.camera:camera-video:1.3.0-beta01")
+    docs("androidx.camera:camera-view:1.3.0-beta01")
+    docs("androidx.camera:camera-viewfinder:1.3.0-beta01")
     docs("androidx.car.app:app:1.4.0-alpha01")
     docs("androidx.car.app:app-automotive:1.4.0-alpha01")
     docs("androidx.car.app:app-projected:1.4.0-alpha01")
@@ -55,55 +55,55 @@
     docs("androidx.cardview:cardview:1.0.0")
     kmpDocs("androidx.collection:collection:1.3.0-alpha03")
     docs("androidx.collection:collection-ktx:1.3.0-alpha03")
-    kmpDocs("androidx.compose.animation:animation:1.5.0-beta02")
-    kmpDocs("androidx.compose.animation:animation-core:1.5.0-beta01")
-    kmpDocs("androidx.compose.animation:animation-graphics:1.5.0-beta02")
-    samples("androidx.compose.animation:animation-samples:1.5.0-beta02")
-    samples("androidx.compose.animation:animation-core-samples:1.5.0-beta02")
-    samples("androidx.compose.animation:animation-graphics-samples:1.5.0-beta02")
-    kmpDocs("androidx.compose.foundation:foundation:1.5.0-beta02")
-    kmpDocs("androidx.compose.foundation:foundation-layout:1.5.0-beta02")
-    samples("androidx.compose.foundation:foundation-layout-samples:1.5.0-beta02")
-    samples("androidx.compose.foundation:foundation-samples:1.5.0-beta02")
-    kmpDocs("androidx.compose.material3:material3:1.2.0-alpha02")
-    samples("androidx.compose.material3:material3-samples:1.2.0-alpha02")
-    kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0-alpha02")
-    samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0-alpha01")
-    kmpDocs("androidx.compose.material:material:1.5.0-beta02")
-    kmpDocs("androidx.compose.material:material-icons-core:1.5.0-beta02")
-    samples("androidx.compose.material:material-icons-core-samples:1.5.0-beta02")
-    kmpDocs("androidx.compose.material:material-ripple:1.5.0-beta02")
-    samples("androidx.compose.material:material-samples:1.5.0-beta02")
-    kmpDocs("androidx.compose.runtime:runtime:1.5.0-beta02")
-    docs("androidx.compose.runtime:runtime-livedata:1.5.0-beta02")
-    samples("androidx.compose.runtime:runtime-livedata-samples:1.5.0-beta02")
-    docs("androidx.compose.runtime:runtime-rxjava2:1.5.0-beta02")
-    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.5.0-beta02")
-    docs("androidx.compose.runtime:runtime-rxjava3:1.5.0-beta02")
-    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.5.0-beta02")
-    kmpDocs("androidx.compose.runtime:runtime-saveable:1.5.0-beta02")
-    samples("androidx.compose.runtime:runtime-saveable-samples:1.5.0-beta02")
-    samples("androidx.compose.runtime:runtime-samples:1.5.0-beta02")
+    kmpDocs("androidx.compose.animation:animation:1.6.0-alpha01")
+    kmpDocs("androidx.compose.animation:animation-core:1.6.0-alpha01")
+    kmpDocs("androidx.compose.animation:animation-graphics:1.6.0-alpha01")
+    samples("androidx.compose.animation:animation-samples:1.6.0-alpha01")
+    samples("androidx.compose.animation:animation-core-samples:1.6.0-alpha01")
+    samples("androidx.compose.animation:animation-graphics-samples:1.6.0-alpha01")
+    kmpDocs("androidx.compose.foundation:foundation:1.6.0-alpha01")
+    kmpDocs("androidx.compose.foundation:foundation-layout:1.6.0-alpha01")
+    samples("androidx.compose.foundation:foundation-layout-samples:1.6.0-alpha01")
+    samples("androidx.compose.foundation:foundation-samples:1.6.0-alpha01")
+    kmpDocs("androidx.compose.material3:material3:1.2.0-alpha03")
+    samples("androidx.compose.material3:material3-samples:1.2.0-alpha03")
+    kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0-alpha03")
+    samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0-alpha03")
+    kmpDocs("androidx.compose.material:material:1.6.0-alpha01")
+    kmpDocs("androidx.compose.material:material-icons-core:1.6.0-alpha01")
+    samples("androidx.compose.material:material-icons-core-samples:1.6.0-alpha01")
+    kmpDocs("androidx.compose.material:material-ripple:1.6.0-alpha01")
+    samples("androidx.compose.material:material-samples:1.6.0-alpha01")
+    kmpDocs("androidx.compose.runtime:runtime:1.6.0-alpha01")
+    docs("androidx.compose.runtime:runtime-livedata:1.6.0-alpha01")
+    samples("androidx.compose.runtime:runtime-livedata-samples:1.6.0-alpha01")
+    docs("androidx.compose.runtime:runtime-rxjava2:1.6.0-alpha01")
+    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.6.0-alpha01")
+    docs("androidx.compose.runtime:runtime-rxjava3:1.6.0-alpha01")
+    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.6.0-alpha01")
+    kmpDocs("androidx.compose.runtime:runtime-saveable:1.6.0-alpha01")
+    samples("androidx.compose.runtime:runtime-saveable-samples:1.6.0-alpha01")
+    samples("androidx.compose.runtime:runtime-samples:1.6.0-alpha01")
     docs("androidx.compose.runtime:runtime-tracing:1.0.0-alpha03")
-    kmpDocs("androidx.compose.ui:ui:1.5.0-beta02")
-    kmpDocs("androidx.compose.ui:ui-geometry:1.5.0-beta01")
-    kmpDocs("androidx.compose.ui:ui-graphics:1.5.0-beta02")
-    samples("androidx.compose.ui:ui-graphics-samples:1.5.0-beta02")
-    kmpDocs("androidx.compose.ui:ui-test:1.5.0-beta02")
-    kmpDocs("androidx.compose.ui:ui-test-junit4:1.5.0-beta02")
-    samples("androidx.compose.ui:ui-test-samples:1.5.0-beta02")
-    kmpDocs("androidx.compose.ui:ui-text:1.5.0-beta02")
-    docs("androidx.compose.ui:ui-text-google-fonts:1.5.0-beta02")
-    samples("androidx.compose.ui:ui-text-samples:1.5.0-beta02")
-    kmpDocs("androidx.compose.ui:ui-tooling:1.5.0-beta02")
-    kmpDocs("androidx.compose.ui:ui-tooling-data:1.5.0-beta02")
-    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.5.0-beta02")
-    kmpDocs("androidx.compose.ui:ui-unit:1.5.0-beta02")
-    samples("androidx.compose.ui:ui-unit-samples:1.5.0-beta02")
-    kmpDocs("androidx.compose.ui:ui-util:1.5.0-beta01")
-    docs("androidx.compose.ui:ui-viewbinding:1.5.0-beta02")
-    samples("androidx.compose.ui:ui-viewbinding-samples:1.5.0-beta02")
-    samples("androidx.compose.ui:ui-samples:1.5.0-beta02")
+    kmpDocs("androidx.compose.ui:ui:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-geometry:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-graphics:1.6.0-alpha01")
+    samples("androidx.compose.ui:ui-graphics-samples:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-test:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-test-junit4:1.6.0-alpha01")
+    samples("androidx.compose.ui:ui-test-samples:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-text:1.6.0-alpha01")
+    docs("androidx.compose.ui:ui-text-google-fonts:1.6.0-alpha01")
+    samples("androidx.compose.ui:ui-text-samples:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-tooling:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-tooling-data:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-unit:1.6.0-alpha01")
+    samples("androidx.compose.ui:ui-unit-samples:1.6.0-alpha01")
+    kmpDocs("androidx.compose.ui:ui-util:1.6.0-alpha01")
+    docs("androidx.compose.ui:ui-viewbinding:1.6.0-alpha01")
+    samples("androidx.compose.ui:ui-viewbinding-samples:1.6.0-alpha01")
+    samples("androidx.compose.ui:ui-samples:1.6.0-alpha01")
     docs("androidx.concurrent:concurrent-futures:1.2.0-alpha01")
     docs("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha01")
     docs("androidx.constraintlayout:constraintlayout:2.2.0-alpha10")
@@ -145,14 +145,14 @@
     docs("androidx.drawerlayout:drawerlayout:1.2.0")
     docs("androidx.dynamicanimation:dynamicanimation:1.1.0-alpha02")
     docs("androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03")
+    docs("androidx.emoji2:emoji2:1.4.0-beta05")
+    docs("androidx.emoji2:emoji2-bundled:1.4.0-beta05")
+    docs("androidx.emoji2:emoji2-emojipicker:1.4.0-beta05")
+    docs("androidx.emoji2:emoji2-views:1.4.0-beta05")
+    docs("androidx.emoji2:emoji2-views-helper:1.4.0-beta05")
     docs("androidx.emoji:emoji:1.2.0-alpha03")
     docs("androidx.emoji:emoji-appcompat:1.2.0-alpha03")
     docs("androidx.emoji:emoji-bundled:1.2.0-alpha03")
-    docs("androidx.emoji2:emoji2:1.4.0-beta04")
-    docs("androidx.emoji2:emoji2-bundled:1.4.0-beta04")
-    docs("androidx.emoji2:emoji2-emojipicker:1.4.0-beta04")
-    docs("androidx.emoji2:emoji2-views:1.4.0-beta04")
-    docs("androidx.emoji2:emoji2-views-helper:1.4.0-beta04")
     docs("androidx.enterprise:enterprise-feedback:1.1.0")
     docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
     docs("androidx.exifinterface:exifinterface:1.3.6")
@@ -169,8 +169,8 @@
     docs("androidx.glance:glance-wear-tiles-preview:1.0.0-alpha06")
     docs("androidx.graphics:graphics-core:1.0.0-alpha04")
     docs("androidx.gridlayout:gridlayout:1.1.0-beta01")
-    docs("androidx.health.connect:connect-client:1.1.0-alpha01")
-    samples("androidx.health.connect:connect-client-samples:1.1.0-alpha01")
+    docs("androidx.health.connect:connect-client:1.1.0-alpha02")
+    samples("androidx.health.connect:connect-client-samples:1.1.0-alpha02")
     docs("androidx.health:health-services-client:1.0.0-beta03")
     docs("androidx.heifwriter:heifwriter:1.1.0-alpha01")
     docs("androidx.hilt:hilt-common:1.0.0-beta01")
@@ -240,8 +240,8 @@
     docs("androidx.media3:media3-transformer:1.1.0-rc01")
     docs("androidx.media3:media3-ui:1.1.0-rc01")
     docs("androidx.media3:media3-ui-leanback:1.1.0-rc01")
-    docs("androidx.mediarouter:mediarouter:1.6.0-alpha04")
-    docs("androidx.mediarouter:mediarouter-testing:1.6.0-alpha04")
+    docs("androidx.mediarouter:mediarouter:1.6.0-alpha05")
+    docs("androidx.mediarouter:mediarouter-testing:1.6.0-alpha05")
     docs("androidx.metrics:metrics-performance:1.0.0-alpha04")
     docs("androidx.navigation:navigation-common:2.7.0-beta01")
     docs("androidx.navigation:navigation-common-ktx:2.7.0-beta01")
@@ -256,18 +256,18 @@
     docs("androidx.navigation:navigation-testing:2.7.0-beta01")
     docs("androidx.navigation:navigation-ui:2.7.0-beta01")
     docs("androidx.navigation:navigation-ui-ktx:2.7.0-beta01")
-    docs("androidx.paging:paging-common:3.2.0-beta01")
-    docs("androidx.paging:paging-common-ktx:3.2.0-beta01")
-    docs("androidx.paging:paging-compose:3.2.0-beta01")
-    samples("androidx.paging:paging-compose-samples:3.2.0-beta01")
-    docs("androidx.paging:paging-guava:3.2.0-beta01")
-    docs("androidx.paging:paging-runtime:3.2.0-beta01")
-    docs("androidx.paging:paging-runtime-ktx:3.2.0-beta01")
-    docs("androidx.paging:paging-rxjava2:3.2.0-beta01")
-    docs("androidx.paging:paging-rxjava2-ktx:3.2.0-beta01")
-    docs("androidx.paging:paging-rxjava3:3.2.0-beta01")
-    samples("androidx.paging:paging-samples:3.2.0-beta01")
-    docs("androidx.paging:paging-testing:3.2.0-beta01")
+    docs("androidx.paging:paging-common:3.2.0-rc01")
+    docs("androidx.paging:paging-common-ktx:3.2.0-rc01")
+    docs("androidx.paging:paging-compose:3.2.0-rc01")
+    samples("androidx.paging:paging-compose-samples:3.2.0-rc01")
+    docs("androidx.paging:paging-guava:3.2.0-rc01")
+    docs("androidx.paging:paging-runtime:3.2.0-rc01")
+    docs("androidx.paging:paging-runtime-ktx:3.2.0-rc01")
+    docs("androidx.paging:paging-rxjava2:3.2.0-rc01")
+    docs("androidx.paging:paging-rxjava2-ktx:3.2.0-rc01")
+    docs("androidx.paging:paging-rxjava3:3.2.0-rc01")
+    samples("androidx.paging:paging-samples:3.2.0-rc01")
+    docs("androidx.paging:paging-testing:3.2.0-rc01")
     docs("androidx.palette:palette:1.0.0")
     docs("androidx.palette:palette-ktx:1.0.0")
     docs("androidx.percentlayout:percentlayout:1.0.1")
@@ -276,15 +276,15 @@
     docs("androidx.print:print:1.1.0-beta01")
     docs("androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05")
     docs("androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05")
-    docs("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha05")
-    docs("androidx.privacysandbox.sdkruntime:sdkruntime-core:1.0.0-alpha05")
+    docs("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha06")
+    docs("androidx.privacysandbox.sdkruntime:sdkruntime-core:1.0.0-alpha06")
     docs("androidx.privacysandbox.tools:tools:1.0.0-alpha04")
     docs("androidx.privacysandbox.tools:tools-apigenerator:1.0.0-alpha04")
     docs("androidx.privacysandbox.tools:tools-apipackager:1.0.0-alpha04")
     docs("androidx.privacysandbox.tools:tools-core:1.0.0-alpha04")
-    docs("androidx.privacysandbox.ui:ui-client:1.0.0-alpha03")
-    docs("androidx.privacysandbox.ui:ui-core:1.0.0-alpha03")
-    docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha03")
+    docs("androidx.privacysandbox.ui:ui-client:1.0.0-alpha04")
+    docs("androidx.privacysandbox.ui:ui-core:1.0.0-alpha04")
+    docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha04")
     docs("androidx.profileinstaller:profileinstaller:1.3.1")
     docs("androidx.recommendation:recommendation:1.0.0")
     docs("androidx.recyclerview:recyclerview:1.3.0")
@@ -292,18 +292,18 @@
     docs("androidx.remotecallback:remotecallback:1.0.0-alpha02")
     docs("androidx.resourceinspection:resourceinspection-annotation:1.0.1")
     docs("androidx.resourceinspection:resourceinspection-processor:1.0.1")
-    docs("androidx.room:room-common:2.6.0-alpha01")
-    docs("androidx.room:room-guava:2.6.0-alpha01")
-    docs("androidx.room:room-ktx:2.6.0-alpha01")
-    docs("androidx.room:room-migration:2.6.0-alpha01")
-    docs("androidx.room:room-paging:2.6.0-alpha01")
-    docs("androidx.room:room-paging-guava:2.6.0-alpha01")
-    docs("androidx.room:room-paging-rxjava2:2.6.0-alpha01")
-    docs("androidx.room:room-paging-rxjava3:2.6.0-alpha01")
-    docs("androidx.room:room-runtime:2.6.0-alpha01")
-    docs("androidx.room:room-rxjava2:2.6.0-alpha01")
-    docs("androidx.room:room-rxjava3:2.6.0-alpha01")
-    docs("androidx.room:room-testing:2.6.0-alpha01")
+    docs("androidx.room:room-common:2.6.0-alpha02")
+    docs("androidx.room:room-guava:2.6.0-alpha02")
+    docs("androidx.room:room-ktx:2.6.0-alpha02")
+    docs("androidx.room:room-migration:2.6.0-alpha02")
+    docs("androidx.room:room-paging:2.6.0-alpha02")
+    docs("androidx.room:room-paging-guava:2.6.0-alpha02")
+    docs("androidx.room:room-paging-rxjava2:2.6.0-alpha02")
+    docs("androidx.room:room-paging-rxjava3:2.6.0-alpha02")
+    docs("androidx.room:room-runtime:2.6.0-alpha02")
+    docs("androidx.room:room-rxjava2:2.6.0-alpha02")
+    docs("androidx.room:room-rxjava3:2.6.0-alpha02")
+    docs("androidx.room:room-testing:2.6.0-alpha02")
     docs("androidx.savedstate:savedstate:1.2.1")
     docs("androidx.savedstate:savedstate-ktx:1.2.1")
     docs("androidx.security:security-app-authenticator:1.0.0-alpha02")
@@ -317,9 +317,9 @@
     docs("androidx.slice:slice-core:1.1.0-alpha02")
     docs("androidx.slice:slice-view:1.1.0-alpha02")
     docs("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
-    docs("androidx.sqlite:sqlite:2.4.0-alpha01")
-    docs("androidx.sqlite:sqlite-framework:2.4.0-alpha01")
-    docs("androidx.sqlite:sqlite-ktx:2.4.0-alpha01")
+    docs("androidx.sqlite:sqlite:2.4.0-alpha02")
+    docs("androidx.sqlite:sqlite-framework:2.4.0-alpha02")
+    docs("androidx.sqlite:sqlite-ktx:2.4.0-alpha02")
     docs("androidx.startup:startup-runtime:1.2.0-alpha02")
     docs("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
     docs("androidx.test:core:1.6.0-alpha01")
@@ -343,9 +343,9 @@
     docs("androidx.test.services:storage:1.5.0-alpha01")
     docs("androidx.test.uiautomator:uiautomator:2.3.0-alpha03")
     docs("androidx.textclassifier:textclassifier:1.0.0-alpha04")
-    docs("androidx.tracing:tracing:1.3.0-alpha01")
-    docs("androidx.tracing:tracing-ktx:1.3.0-alpha01")
-    docs("androidx.tracing:tracing-perfetto:1.0.0-alpha16")
+    docs("androidx.tracing:tracing:1.3.0-alpha02")
+    docs("androidx.tracing:tracing-ktx:1.3.0-alpha02")
+    docs("androidx.tracing:tracing-perfetto:1.0.0-alpha17")
     docs("androidx.tracing:tracing-perfetto-common:1.0.0-alpha16") // TODO(243405142) clean-up
     docs("androidx.transition:transition:1.5.0-alpha01")
     docs("androidx.transition:transition-ktx:1.5.0-alpha01")
@@ -359,59 +359,59 @@
     docs("androidx.versionedparcelable:versionedparcelable:1.1.1")
     docs("androidx.viewpager2:viewpager2:1.1.0-beta02")
     docs("androidx.viewpager:viewpager:1.1.0-alpha01")
-    docs("androidx.wear.compose:compose-foundation:1.2.0-beta02")
-    samples("androidx.wear.compose:compose-foundation-samples:1.2.0-beta02")
-    docs("androidx.wear.compose:compose-material:1.2.0-beta02")
-    docs("androidx.wear.compose:compose-material-core:1.2.0-beta02")
-    samples("androidx.wear.compose:compose-material-samples:1.2.0-beta02")
-    docs("androidx.wear.compose:compose-material3:1.0.0-alpha06")
-    samples("androidx.wear.compose:compose-material3-samples:1.2.0-beta02")
-    docs("androidx.wear.compose:compose-navigation:1.2.0-beta02")
-    samples("androidx.wear.compose:compose-navigation-samples:1.2.0-beta02")
-    docs("androidx.wear.compose:compose-ui-tooling:1.2.0-beta02")
-    docs("androidx.wear.protolayout:protolayout:1.0.0-alpha11")
-    docs("androidx.wear.protolayout:protolayout-expression:1.0.0-alpha11")
-    docs("androidx.wear.protolayout:protolayout-material:1.0.0-alpha11")
-    docs("androidx.wear.protolayout:protolayout-renderer:1.0.0-alpha11")
-    docs("androidx.wear.tiles:tiles:1.2.0-alpha07")
-    docs("androidx.wear.tiles:tiles-material:1.2.0-alpha07")
-    docs("androidx.wear.tiles:tiles-proto:1.2.0-alpha07")
-    docs("androidx.wear.tiles:tiles-renderer:1.2.0-alpha07")
-    docs("androidx.wear.tiles:tiles-testing:1.2.0-alpha07")
+    docs("androidx.wear.compose:compose-foundation:1.3.0-alpha01")
+    samples("androidx.wear.compose:compose-foundation-samples:1.3.0-alpha01")
+    docs("androidx.wear.compose:compose-material:1.3.0-alpha01")
+    docs("androidx.wear.compose:compose-material-core:1.3.0-alpha01")
+    samples("androidx.wear.compose:compose-material-samples:1.3.0-alpha01")
+    docs("androidx.wear.compose:compose-material3:1.0.0-alpha07")
+    samples("androidx.wear.compose:compose-material3-samples:1.3.0-alpha01")
+    docs("androidx.wear.compose:compose-navigation:1.3.0-alpha01")
+    samples("androidx.wear.compose:compose-navigation-samples:1.3.0-alpha01")
+    docs("androidx.wear.compose:compose-ui-tooling:1.3.0-alpha01")
+    docs("androidx.wear.protolayout:protolayout:1.0.0-beta01")
+    docs("androidx.wear.protolayout:protolayout-expression:1.0.0-beta01")
+    docs("androidx.wear.protolayout:protolayout-material:1.0.0-beta01")
+    docs("androidx.wear.protolayout:protolayout-renderer:1.0.0-beta01")
+    docs("androidx.wear.tiles:tiles:1.2.0-beta01")
+    docs("androidx.wear.tiles:tiles-material:1.2.0-beta01")
+    docs("androidx.wear.tiles:tiles-proto:1.2.0-beta01")
+    docs("androidx.wear.tiles:tiles-renderer:1.2.0-beta01")
+    docs("androidx.wear.tiles:tiles-testing:1.2.0-beta01")
     docs("androidx.wear.tiles:tiles-tooling:1.2.0-alpha07")
-    docs("androidx.wear.watchface:watchface:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-client:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-client-guava:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-complications:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-complications-data:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-complications-data-source:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-complications-rendering:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-data:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-editor:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-editor-guava:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-guava:1.2.0-alpha08")
-    samples("androidx.wear.watchface:watchface-samples:1.2.0-alpha08")
-    docs("androidx.wear.watchface:watchface-style:1.2.0-alpha08")
-    docs("androidx.wear:wear:1.3.0-beta01")
+    docs("androidx.wear.watchface:watchface:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-client:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-client-guava:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-complications:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-complications-data:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-complications-data-source:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-complications-rendering:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-data:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-editor:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-editor-guava:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-guava:1.2.0-alpha09")
+    samples("androidx.wear.watchface:watchface-samples:1.2.0-alpha09")
+    docs("androidx.wear.watchface:watchface-style:1.2.0-alpha09")
+    docs("androidx.wear:wear:1.3.0-rc01")
     stubs(fileTree(dir: "../wear/wear_stubs/", include: ["com.google.android.wearable-stubs.jar"]))
     docs("androidx.wear:wear-input:1.2.0-alpha02")
     samples("androidx.wear:wear-input-samples:1.2.0-alpha01")
     docs("androidx.wear:wear-input-testing:1.2.0-alpha02")
     docs("androidx.wear:wear-ongoing:1.1.0-alpha01")
     docs("androidx.wear:wear-phone-interactions:1.1.0-alpha03")
-    docs("androidx.wear:wear-remote-interactions:1.0.0")
+    docs("androidx.wear:wear-remote-interactions:1.1.0-alpha01")
     docs("androidx.webkit:webkit:1.8.0-alpha01")
     docs("androidx.window.extensions.core:core:1.0.0")
-    docs("androidx.window:window:1.2.0-alpha02")
+    docs("androidx.window:window:1.2.0-alpha03")
     stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
-    docs("androidx.window:window-core:1.2.0-alpha02")
+    docs("androidx.window:window-core:1.2.0-alpha03")
     stubs("androidx.window:window-extensions:1.0.0-alpha01")
-    docs("androidx.window:window-java:1.2.0-alpha02")
-    docs("androidx.window:window-rxjava2:1.2.0-alpha02")
-    docs("androidx.window:window-rxjava3:1.2.0-alpha02")
-    samples("androidx.window:window-samples:1.2.0-alpha02")
-    docs("androidx.window:window-testing:1.2.0-alpha02")
+    docs("androidx.window:window-java:1.2.0-alpha03")
+    docs("androidx.window:window-rxjava2:1.2.0-alpha03")
+    docs("androidx.window:window-rxjava3:1.2.0-alpha03")
+    samples("androidx.window:window-samples:1.2.0-alpha03")
+    docs("androidx.window:window-testing:1.2.0-alpha03")
     docs("androidx.work:work-gcm:2.9.0-alpha01")
     docs("androidx.work:work-multiprocess:2.9.0-alpha01")
     docs("androidx.work:work-runtime:2.9.0-alpha01")
diff --git a/draganddrop/OWNERS b/draganddrop/OWNERS
index 7de3c23..a3e7f8f 100644
--- a/draganddrop/OWNERS
+++ b/draganddrop/OWNERS
@@ -1 +1,2 @@
+# Bug component: 1333601
 rledley@google.com
diff --git a/drawerlayout/OWNERS b/drawerlayout/OWNERS
index 347e71c..7b04b71 100644
--- a/drawerlayout/OWNERS
+++ b/drawerlayout/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 461408
 aelias@google.com
 ryanmentley@google.com
diff --git a/fragment/fragment-ktx/OWNERS b/fragment/fragment-ktx/OWNERS
index 6fd8227..89a4719 100644
--- a/fragment/fragment-ktx/OWNERS
+++ b/fragment/fragment-ktx/OWNERS
@@ -1 +1,2 @@
+# Bug component: 461227
 yboyar@google.com
diff --git a/fragment/fragment-ktx/build.gradle b/fragment/fragment-ktx/build.gradle
index ad0cc6e..2c3859d 100644
--- a/fragment/fragment-ktx/build.gradle
+++ b/fragment/fragment-ktx/build.gradle
@@ -25,7 +25,7 @@
 
 dependencies {
     api(project(":fragment:fragment"))
-    api(project(":activity:activity-ktx")) {
+    api(projectOrArtifact(":activity:activity-ktx")) {
         because "Mirror fragment dependency graph for -ktx artifacts"
     }
     api("androidx.core:core-ktx:1.2.0") {
@@ -62,4 +62,4 @@
 
 android {
     namespace "androidx.fragment.ktx"
-}
\ No newline at end of file
+}
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index b16457b..ed38f46 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -29,7 +29,7 @@
     api("androidx.collection:collection:1.1.0")
     api("androidx.viewpager:viewpager:1.0.0")
     api("androidx.loader:loader:1.0.0")
-    api(project(":activity:activity"))
+    api(projectOrArtifact(":activity:activity"))
     api("androidx.lifecycle:lifecycle-runtime:2.6.1")
     api("androidx.lifecycle:lifecycle-livedata-core:2.6.1")
     api("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 5c393da..37ed965 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -730,8 +730,15 @@
         // This FragmentManager needs to have a back stack for this to be enabled
         // And the parent fragment, if it exists, needs to be the primary navigation
         // fragment.
-        mOnBackPressedCallback.setEnabled(getBackStackEntryCount() > 0
-                && isPrimaryNavigation(mParent));
+        boolean isEnabled = getBackStackEntryCount() > 0
+                && isPrimaryNavigation(mParent);
+        if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
+            Log.d(FragmentManager.TAG,
+                    "OnBackPressedCallback for FragmentManager " + this + " enabled state is "
+                            + isEnabled
+            );
+        }
+        mOnBackPressedCallback.setEnabled(isEnabled);
     }
 
     /**
diff --git a/glance/glance-appwidget/samples/src/main/java/androidx/glance/appwidget/samples/GlanceAppWidgetSamples.kt b/glance/glance-appwidget/samples/src/main/java/androidx/glance/appwidget/samples/GlanceAppWidgetSamples.kt
index c290ed7..a31aa95 100644
--- a/glance/glance-appwidget/samples/src/main/java/androidx/glance/appwidget/samples/GlanceAppWidgetSamples.kt
+++ b/glance/glance-appwidget/samples/src/main/java/androidx/glance/appwidget/samples/GlanceAppWidgetSamples.kt
@@ -14,27 +14,68 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalGlanceApi::class)
+
 package androidx.glance.appwidget.samples
 
 import android.content.Context
 import androidx.annotation.Sampled
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import androidx.glance.ExperimentalGlanceApi
 import androidx.glance.GlanceId
+import androidx.glance.GlanceModifier
+import androidx.glance.action.clickable
 import androidx.glance.appwidget.GlanceAppWidget
 import androidx.glance.appwidget.provideContent
+import androidx.glance.appwidget.updateAll
 import androidx.glance.text.Text
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
 
 @Sampled
 fun provideGlanceSample() {
     class MyWidget : GlanceAppWidget() {
+
+        val Context.myWidgetStore by preferencesDataStore("MyWidget")
+        val Name = stringPreferencesKey("name")
+
         override suspend fun provideGlance(context: Context, id: GlanceId) {
-            // Load data needed to render the AppWidget here. Prefer doing heavy work before
+            // Load initial data needed to render the AppWidget here. Prefer doing heavy work before
             // provideContent, as the provideGlance function will timeout shortly after
             // provideContent is called.
+            val store = context.myWidgetStore
+            val initial = store.data.first()
 
             provideContent {
-                // Declare your @Composable layout here.
-                Text("Hello World")
+                // Observe your sources of data, and declare your @Composable layout.
+                val data by store.data.collectAsState(initial)
+                val scope = rememberCoroutineScope()
+                Text(
+                    text = "Hello ${data[Name]}",
+                    modifier = GlanceModifier.clickable("changeName") {
+                        scope.launch {
+                            store.updateData {
+                                it.toMutablePreferences().apply { set(Name, "Changed") }
+                            }
+                        }
+                    }
+                )
             }
         }
+
+        // Updating the widget from elsewhere in the app:
+        suspend fun changeWidgetName(context: Context, newName: String) {
+            context.myWidgetStore.updateData {
+                it.toMutablePreferences().apply { set(Name, newName) }
+            }
+            // Call update/updateAll in case a Worker for the widget is not currently running. This
+            // is not necessary when updating data from inside of the composition using lambdas,
+            // since a Worker will be started to run lambda actions.
+            MyWidget().updateAll(context)
+        }
     }
 }
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
index 22a7cc9..5d69b49 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
@@ -57,6 +57,21 @@
      * This is a good place to load any data needed to render the Composable. Use
      * [provideContent] to provide the Composable once the data is ready.
      *
+     * [provideGlance] is run in the background as a [androidx.work.CoroutineWorker] in response to
+     * calls to [update] and [updateAll], as well as requests from the Launcher. Before
+     * `provideContent` is called, `provideGlance` is subject to the typical
+     * [androidx.work.WorkManager] time limit (currently ten minutes). After `provideContent` is
+     * called, the composition continues to run and recompose for about 45 seconds. When UI
+     * interactions or update requests are received, additional time is added to process these
+     * requests.
+     *
+     * Note: [update] and [updateAll] do not restart `provideGlance` if it is already running. As a
+     * result, you should load initial data before calling `provideContent`, and then observe your
+     * sources of data within the composition (e.g. [androidx.compose.runtime.collectAsState]). This
+     * ensures that your widget will continue to update while the composition is active. When you
+     * update your data source from elsewhere in the app, make sure to call `update` in case a
+     * Worker for this widget is not currently running.
+     *
      * @sample androidx.glance.appwidget.samples.provideGlanceSample
      */
     abstract suspend fun provideGlance(
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d22d69d..e9e0b2e 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,13 +2,13 @@
 # -----------------------------------------------------------------------------
 # All of the following should be updated in sync.
 # -----------------------------------------------------------------------------
-androidGradlePlugin = "8.1.0-beta02"
+androidGradlePlugin = "8.1.0-beta05"
 # NOTE: When updating the lint version we also need to update the `api` version
 # supported by `IssueRegistry`'s.' For e.g. r.android.com/1331903
-androidLint = "31.1.0-beta02"
+androidLint = "31.1.0-beta05"
 # Once you have a chosen version of AGP to upgrade to, go to
 # https://developer.android.com/studio/archive and find the matching version of Studio.
-androidStudio = "2022.3.1.13"
+androidStudio = "2022.3.1.16"
 # -----------------------------------------------------------------------------
 
 androidGradlePluginMin = "7.0.4"
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLFrameBufferRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLFrameBufferRendererTest.kt
index ae82a1e..e7f6682 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLFrameBufferRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLFrameBufferRendererTest.kt
@@ -45,6 +45,7 @@
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -243,6 +244,7 @@
         }
     }
 
+    @Ignore // b/288580549
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     @Test
     fun testSetUsageFlags() {
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/units/Energy.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/units/Energy.kt
index 606b7c2..a9096b1 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/units/Energy.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/units/Energy.kt
@@ -90,7 +90,7 @@
         @JvmStatic fun joules(value: Double): Energy = Energy(value, Type.JOULES)
 
         /** Creates [Energy] with the specified value in kilojoules. */
-        @JvmStatic fun kilojoules(value: Double): Energy = Energy(value, Type.KILOCALORIES)
+        @JvmStatic fun kilojoules(value: Double): Energy = Energy(value, Type.KILOJOULES)
     }
 
     private enum class Type {
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/units/EnergyTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/units/EnergyTest.kt
index b7449012..e18b21e 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/units/EnergyTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/units/EnergyTest.kt
@@ -52,4 +52,32 @@
             .that(Energy.kilocalories(235.001).hashCode())
             .isNotEqualTo(Energy.calories(235_000.0).hashCode())
     }
+
+    @Test
+    fun calories_roundTrip() {
+        expect
+            .that(Energy.calories(1.0).inCalories)
+            .isEqualTo(1.0)
+    }
+
+    @Test
+    fun kilocalories_roundTrip() {
+        expect
+            .that(Energy.kilocalories(1.0).inKilocalories)
+            .isEqualTo(1.0)
+    }
+
+    @Test
+    fun joules_roundTrip() {
+        expect
+            .that(Energy.joules(1.0).inJoules)
+            .isEqualTo(1.0)
+    }
+
+    @Test
+    fun kilojoules_roundTrip() {
+        expect
+            .that(Energy.kilojoules(1.0).inKilojoules)
+            .isEqualTo(1.0)
+    }
 }
diff --git a/heifwriter/heifwriter/src/main/java/androidx/heifwriter/AvifEncoder.java b/heifwriter/heifwriter/src/main/java/androidx/heifwriter/AvifEncoder.java
index 6fdae4d..b245f57 100644
--- a/heifwriter/heifwriter/src/main/java/androidx/heifwriter/AvifEncoder.java
+++ b/heifwriter/heifwriter/src/main/java/androidx/heifwriter/AvifEncoder.java
@@ -55,6 +55,8 @@
 
     protected static final int GRID_WIDTH = 512;
     protected static final int GRID_HEIGHT = 512;
+    // Block size for AV1 encoder
+    protected static final int ENCODING_BLOCK_SIZE = 64;
     protected static final double MAX_COMPRESS_RATIO = 0.25f;
 
     private static final MediaCodecList sMCL =
diff --git a/heifwriter/heifwriter/src/main/java/androidx/heifwriter/EncoderBase.java b/heifwriter/heifwriter/src/main/java/androidx/heifwriter/EncoderBase.java
index c8ac8d1..52ec08b 100644
--- a/heifwriter/heifwriter/src/main/java/androidx/heifwriter/EncoderBase.java
+++ b/heifwriter/heifwriter/src/main/java/androidx/heifwriter/EncoderBase.java
@@ -76,6 +76,7 @@
     private String MIME;
     private int GRID_WIDTH;
     private int GRID_HEIGHT;
+    private int ENCODING_BLOCK_SIZE;
     private double MAX_COMPRESS_RATIO;
     private int INPUT_BUFFER_POOL_SIZE = 2;
 
@@ -214,12 +215,14 @@
                 MIME = mimeType;
                 GRID_WIDTH = HeifEncoder.GRID_WIDTH;
                 GRID_HEIGHT = HeifEncoder.GRID_HEIGHT;
+                ENCODING_BLOCK_SIZE = HeifEncoder.ENCODING_BLOCK_SIZE;
                 MAX_COMPRESS_RATIO = HeifEncoder.MAX_COMPRESS_RATIO;
                 break;
             case "AVIF":
                 MIME = mimeType;
                 GRID_WIDTH = AvifEncoder.GRID_WIDTH;
                 GRID_HEIGHT = AvifEncoder.GRID_HEIGHT;
+                ENCODING_BLOCK_SIZE = AvifEncoder.ENCODING_BLOCK_SIZE;
                 MAX_COMPRESS_RATIO = AvifEncoder.MAX_COMPRESS_RATIO;
                 break;
             default:
@@ -298,7 +301,8 @@
             gridRows = (height + GRID_HEIGHT - 1) / GRID_HEIGHT;
             gridCols = (width + GRID_WIDTH - 1) / GRID_WIDTH;
         } else {
-            gridWidth = mWidth;
+            gridWidth = (mWidth + ENCODING_BLOCK_SIZE - 1)
+                    / ENCODING_BLOCK_SIZE * ENCODING_BLOCK_SIZE;
             gridHeight = mHeight;
             gridRows = 1;
             gridCols = 1;
diff --git a/heifwriter/heifwriter/src/main/java/androidx/heifwriter/HeifEncoder.java b/heifwriter/heifwriter/src/main/java/androidx/heifwriter/HeifEncoder.java
index a0b0e9a..7b54b4a 100644
--- a/heifwriter/heifwriter/src/main/java/androidx/heifwriter/HeifEncoder.java
+++ b/heifwriter/heifwriter/src/main/java/androidx/heifwriter/HeifEncoder.java
@@ -52,6 +52,8 @@
 
     protected static final int GRID_WIDTH = 512;
     protected static final int GRID_HEIGHT = 512;
+    // Block size for HEVC encoder
+    protected static final int ENCODING_BLOCK_SIZE = 32;
     protected static final double MAX_COMPRESS_RATIO = 0.25f;
 
     private static final MediaCodecList sMCL =
diff --git a/input/OWNERS b/input/OWNERS
index 492d141..3c18db8 100644
--- a/input/OWNERS
+++ b/input/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 1334004
 joseprio@google.com
 michaelwr@google.com
 svv@google.com
\ No newline at end of file
diff --git a/libraryversions.toml b/libraryversions.toml
index b029dcb..5e3b152 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -20,8 +20,8 @@
 CAR_APP = "1.4.0-alpha01"
 COLLECTION = "1.3.0-alpha05"
 COMPOSE = "1.6.0-alpha01"
-COMPOSE_COMPILER = "1.4.7"
-COMPOSE_MATERIAL3 = "1.2.0-alpha03"
+COMPOSE_COMPILER = "1.4.8"
+COMPOSE_MATERIAL3 = "1.2.0-alpha04"
 COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha01"
 COMPOSE_RUNTIME_TRACING = "1.0.0-alpha03"
 CONSTRAINTLAYOUT = "2.2.0-alpha11"
diff --git a/lifecycle/lifecycle-reactivestreams-ktx/OWNERS b/lifecycle/lifecycle-reactivestreams-ktx/OWNERS
index 6fd8227..b66139d 100644
--- a/lifecycle/lifecycle-reactivestreams-ktx/OWNERS
+++ b/lifecycle/lifecycle-reactivestreams-ktx/OWNERS
@@ -1 +1,2 @@
+# Bug component: 461459
 yboyar@google.com
diff --git a/loader/loader-ktx/OWNERS b/loader/loader-ktx/OWNERS
index 6fd8227..e02f5ed 100644
--- a/loader/loader-ktx/OWNERS
+++ b/loader/loader-ktx/OWNERS
@@ -1 +1,2 @@
+# Bug component: 461381
 yboyar@google.com
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaControllerCallback.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaControllerCallback.aidl
index 628871f..4e2b72f 100644
--- a/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaControllerCallback.aidl
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaControllerCallback.aidl
@@ -31,7 +31,7 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package android.support.v4.media.session;
-/* @hide */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 interface IMediaControllerCallback {
   oneway void onEvent(String event, in android.os.Bundle extras);
   oneway void onSessionDestroyed();
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaSession.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaSession.aidl
index 0a33361..9c03f05 100644
--- a/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaSession.aidl
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaSession.aidl
@@ -31,7 +31,7 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package android.support.v4.media.session;
-/* @hide */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 interface IMediaSession {
   void sendCommand(String command, in android.os.Bundle args, in android.support.v4.media.session.MediaSessionCompat.ResultReceiverWrapper cb) = 0;
   boolean sendMediaButton(in android.view.KeyEvent mediaButton) = 1;
diff --git a/media/media/lint-baseline.xml b/media/media/lint-baseline.xml
index 83d9e78..0d822ce 100644
--- a/media/media/lint-baseline.xml
+++ b/media/media/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta01)" variant="all" version="8.1.0-beta01">
+<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
 
     <issue
         id="PrereleaseSdkCoreDependency"
diff --git a/media/media/src/main/aidl/android/support/v4/media/session/IMediaControllerCallback.aidl b/media/media/src/main/aidl/android/support/v4/media/session/IMediaControllerCallback.aidl
index 6777a3f..e7ae114 100644
--- a/media/media/src/main/aidl/android/support/v4/media/session/IMediaControllerCallback.aidl
+++ b/media/media/src/main/aidl/android/support/v4/media/session/IMediaControllerCallback.aidl
@@ -24,8 +24,8 @@
 /**
  * Callback interface for a MediaSessionCompat to send updates to a
  * MediaControllerCompat. This is only used on pre-Lollipop systems.
- * @hide
  */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 oneway interface IMediaControllerCallback {
     void onEvent(String event, in Bundle extras);
     void onSessionDestroyed();
diff --git a/media/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl b/media/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl
index 2b46a0e..e8b51fe 100644
--- a/media/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl
+++ b/media/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl
@@ -32,8 +32,8 @@
 
 /**
  * Interface to a MediaSessionCompat.
- * @hide
  */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 interface IMediaSession {
     // Next ID: 50
     void sendCommand(String command, in Bundle args, in MediaSessionCompat.ResultReceiverWrapper cb) = 0;
diff --git a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaController.aidl b/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaController.aidl
index a5a8e2d6..c437a9d 100644
--- a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaController.aidl
+++ b/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaController.aidl
@@ -32,7 +32,7 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package androidx.media2.session;
-/* @hide */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 interface IMediaController {
   oneway void onCurrentMediaItemChanged(int seq, in androidx.versionedparcelable.ParcelImpl item, int currentIdx, int previousIdx, int nextIdx) = 0;
   oneway void onPlayerStateChanged(int seq, long eventTimeMs, long positionMs, int state) = 1;
diff --git a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSession.aidl b/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSession.aidl
index 751cbfa..6abb4dc 100644
--- a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSession.aidl
+++ b/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSession.aidl
@@ -32,7 +32,7 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package androidx.media2.session;
-/* @hide */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 interface IMediaSession {
   oneway void connect(androidx.media2.session.IMediaController caller, int seq, in androidx.versionedparcelable.ParcelImpl connectionRequest) = 0;
   oneway void release(androidx.media2.session.IMediaController caller, int seq) = 1;
diff --git a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSessionService.aidl b/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSessionService.aidl
index e661871..6a093d7 100644
--- a/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSessionService.aidl
+++ b/media2/media2-session/api/aidlRelease/current/androidx/media2/session/IMediaSessionService.aidl
@@ -32,7 +32,7 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package androidx.media2.session;
-/* @hide */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 interface IMediaSessionService {
   oneway void connect(androidx.media2.session.IMediaController caller, in androidx.versionedparcelable.ParcelImpl connectionRequest) = 0;
 }
diff --git a/media2/media2-session/lint-baseline.xml b/media2/media2-session/lint-baseline.xml
index e52b867..bcb6051 100644
--- a/media2/media2-session/lint-baseline.xml
+++ b/media2/media2-session/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-beta03" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.0.0-beta03">
+<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
 
     <issue
         id="WrongConstant"
@@ -38,33 +38,6 @@
     </issue>
 
     <issue
-        id="RequireUnstableAidlAnnotation"
-        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
-        errorLine1="oneway interface IMediaController {"
-        errorLine2="^">
-        <location
-            file="src/main/aidl/androidx/media2/session/IMediaController.aidl"/>
-    </issue>
-
-    <issue
-        id="RequireUnstableAidlAnnotation"
-        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
-        errorLine1="oneway interface IMediaSession {"
-        errorLine2="^">
-        <location
-            file="src/main/aidl/androidx/media2/session/IMediaSession.aidl"/>
-    </issue>
-
-    <issue
-        id="RequireUnstableAidlAnnotation"
-        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
-        errorLine1="oneway interface IMediaSessionService {"
-        errorLine2="^">
-        <location
-            file="src/main/aidl/androidx/media2/session/IMediaSessionService.aidl"/>
-    </issue>
-
-    <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    public void setTimeDiff(Long timeDiff) {"
diff --git a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaController.aidl b/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaController.aidl
index 67b4401..dc78b89 100644
--- a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaController.aidl
+++ b/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaController.aidl
@@ -27,8 +27,8 @@
  * <p>
  * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
  * and holds calls from session to make session owner(s) frozen.
- * @hide
  */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 oneway interface IMediaController {
     void onCurrentMediaItemChanged(int seq, in ParcelImpl item, int currentIdx, int previousIdx,
             int nextIdx) = 0;
diff --git a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSession.aidl b/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSession.aidl
index c6dfa57..5f54a89 100644
--- a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSession.aidl
+++ b/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSession.aidl
@@ -29,8 +29,8 @@
  * <p>
  * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
  * and holds calls from session to make session owner(s) frozen.
- * @hide
  */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 oneway interface IMediaSession {
     void connect(IMediaController caller, int seq, in ParcelImpl connectionRequest) = 0;
     void release(IMediaController caller, int seq) = 1;
diff --git a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSessionService.aidl b/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSessionService.aidl
index 46b4594..190723e 100644
--- a/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSessionService.aidl
+++ b/media2/media2-session/src/main/stableAidl/androidx/media2/session/IMediaSessionService.aidl
@@ -24,8 +24,8 @@
  * <p>
  * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
  * and holds calls from session to make session owner(s) frozen.
- * @hide
  */
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
 oneway interface IMediaSessionService {
     void connect(IMediaController caller, in ParcelImpl connectionRequest) = 0;
     // Next Id : 1
diff --git a/media2/media2-widget/src/main/res/values-fa/strings.xml b/media2/media2-widget/src/main/res/values-fa/strings.xml
index 0eec54c..058ed11 100644
--- a/media2/media2-widget/src/main/res/values-fa/strings.xml
+++ b/media2/media2-widget/src/main/res/values-fa/strings.xml
@@ -32,7 +32,7 @@
     <string name="mcv2_playback_error_text" msgid="6061787693725630293">"مورد درخواستی پخش نشد"</string>
     <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"تأیید"</string>
     <string name="mcv2_back_button_desc" msgid="1540894858499118373">"برگشت"</string>
-    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"فهرست دکمه برگشت به عقب"</string>
+    <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"فهرست دکمه برگشتن به‌عقب"</string>
     <string name="mcv2_overflow_right_button_desc" msgid="7388732945289831383">"مشاهده دکمه‌های بیشتر"</string>
     <string name="mcv2_seek_bar_desc" msgid="24915699029009384">"پیشرفت بازپخش"</string>
     <string name="mcv2_settings_button_desc" msgid="811917224044739656">"تنظیمات"</string>
diff --git a/mediarouter/mediarouter/src/main/res/values-af/strings.xml b/mediarouter/mediarouter/src/main/res/values-af/strings.xml
index 2560d60..135ef9d 100644
--- a/mediarouter/mediarouter/src/main/res/values-af/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-af/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Saai uit. Gekoppel"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Saai uit na"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Soek tans toestelle"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Soek tans na toestelle …"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Ontkoppel"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Hou op uitsaai"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Maak toe"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Speel op \'n groep"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Geen inligting beskikbaar nie"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Geen toestelle beskikbaar nie"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Kom meer te wete"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Leer hoe om uit te saai"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-am/strings.xml b/mediarouter/mediarouter/src/main/res/values-am/strings.xml
index b8d4461..77db829 100644
--- a/mediarouter/mediarouter/src/main/res/values-am/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-am/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast። ተገናኝቷል"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Cast አድርግ ወደ"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"መሣሪያዎችን በማግኘት ላይ"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"መሣሪያዎችን በመፈለግ ላይ…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ግንኙነት አቋርጥ"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Cast ማድረግ አቁም"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"ዝጋ"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"በቡድን ላይ ያጫውቱ"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"ምንም መረጃ አይገኝም"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"ምንም መሣሪያዎች አይገኙም"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"የበለጠ ለመረዳት"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"እንዴት cast እንደሚያደርጉ ይወቁ"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ar/strings.xml b/mediarouter/mediarouter/src/main/res/values-ar/strings.xml
index 4fc753a..a9d0a25 100644
--- a/mediarouter/mediarouter/src/main/res/values-ar/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ar/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"إرسال متصل"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"البث إلى"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"جارٍ البحث عن أجهزة"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"جارٍ البحث عن أجهزة…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"قطع اتصال"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"إيقاف البث"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"إغلاق"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"تشغيل على مجموعة من الأجهزة"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"لا تتوفر أي معلومات"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"لا تتوفّر أي أجهزة"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"مزيد من المعلومات"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"التعرُّف على كيفية البثّ"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-as/strings.xml b/mediarouter/mediarouter/src/main/res/values-as/strings.xml
index c732471..8e5c400 100644
--- a/mediarouter/mediarouter/src/main/res/values-as/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-as/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"কাষ্ট কৰক। সংযুক্ত হৈ আছে"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"ইয়াত কাষ্ট কৰক"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"ডিভাইচ বিচাৰি থকা হৈছে"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"ডিভাইচ বিচাৰি থকা হৈছে..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"বিচ্ছিন্ন কৰক"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"কাষ্ট কৰা বন্ধ কৰক"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"বন্ধ কৰক"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"কোনো এটা গোটত প্লে’ কৰক"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"কোনো তথ্য নাই"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"কোনো ডিভাইচ উপলব্ধ নহয়"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"অধিক জানক"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"কেনেকৈ কাষ্ট কৰিব লাগে জানক"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-az/strings.xml b/mediarouter/mediarouter/src/main/res/values-az/strings.xml
index c66e26c..a79017f 100644
--- a/mediarouter/mediarouter/src/main/res/values-az/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-az/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Yayım. Qoşulub"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Bura yayımlayın"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Cihazlar axtarılır"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Cihazlar axtarılır..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Əlaqəni silin"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Yayımı dayandırın"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Bağlayın"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Qrupda oxudun"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Əlçatan məlumat yoxdur"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Əlçatan cihaz yoxdur"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Ətraflı məlumat"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Yayım qaydasını öyrənin"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-b+sr+Latn/strings.xml b/mediarouter/mediarouter/src/main/res/values-b+sr+Latn/strings.xml
index a8903dd..783f95f 100644
--- a/mediarouter/mediarouter/src/main/res/values-b+sr+Latn/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-b+sr+Latn/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Prebacite. Povezan je"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Prebacite na"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Traže se uređaji"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Traže se uređaji…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Prekini vezu"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Zaustavi prebacivanje"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Zatvori"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Puštajte u grupi"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nema dostupnih informacija"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nije dostupan nijedan uređaj"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Saznajte više"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Saznajte kako da prebacujete"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-be/strings.xml b/mediarouter/mediarouter/src/main/res/values-be/strings.xml
index 6cb1b82..911084c 100644
--- a/mediarouter/mediarouter/src/main/res/values-be/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-be/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Трансліраваць. Падключана"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Трансліраваць на прыладу"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Пошук прылад"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Ідзе пошук прылад…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Адлучыць"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Спыніць трансляцыю"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Закрыць"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Прайграць на групе прылад"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Інфармацыя адсутнічае"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Няма даступных прылад"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Даведацца больш"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Як пачаць трансляцыю"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-bg/strings.xml b/mediarouter/mediarouter/src/main/res/values-bg/strings.xml
index fd34fe6..c58b16f 100644
--- a/mediarouter/mediarouter/src/main/res/values-bg/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-bg/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Предаване. Установена е връзка"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Предаване към"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Търсят се устройства"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Търсят се устройства..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Прекратяване на връзката"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Спиране на предаването"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Затваряне"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Възпроизвеждане в група"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Няма налична информация"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Няма налични устройства"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Научете повече"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Научете как да предавате"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-bn/strings.xml b/mediarouter/mediarouter/src/main/res/values-bn/strings.xml
index 3fffb29..60d52a0 100644
--- a/mediarouter/mediarouter/src/main/res/values-bn/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-bn/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"কাস্ট করুন। কানেক্ট করা আছে"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"এখানে কাস্ট করুন:"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"ডিভাইস খোঁজা হচ্ছে"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"ডিভাইস খোঁজা হচ্ছে…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"কানেকশন বিচ্ছিন্ন করুন"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"কাস্ট করা বন্ধ করুন"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"বন্ধ করুন"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"গ্রুপে প্লে করুন"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"কোনও তথ্য নেই"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"কোনও ডিভাইস উপলভ্য নেই"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"আরও জানুন"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"কীভাবে কাস্ট করবেন তা জানুন"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-bs/strings.xml b/mediarouter/mediarouter/src/main/res/values-bs/strings.xml
index 9c1b595..633736b 100644
--- a/mediarouter/mediarouter/src/main/res/values-bs/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-bs/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Emitiranje. Povezano"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Emitiranje na"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Traženje uređaja"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Traženje uređaja…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Prekini vezu"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Zaustavi emitiranje"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Zatvori"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Reproduciranje u grupi"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nema dostupnih informacija"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nema dostupnih uređaja"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Saznajte više"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Saznajte kako emitirati"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ca/strings.xml b/mediarouter/mediarouter/src/main/res/values-ca/strings.xml
index 64b425b..3e998bd 100644
--- a/mediarouter/mediarouter/src/main/res/values-ca/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ca/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Emet. Connectat."</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Emet contingut a"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"S\'estan cercant dispositius"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"S\'estan cercant dispositius..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Desconnecta"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Atura l\'emissió"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Tanca"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Reprodueix en un grup"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"No hi ha informació disponible"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"No hi ha cap dispositiu disponible"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Més informació"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Informació sobre com pots emetre contingut"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-cs/strings.xml b/mediarouter/mediarouter/src/main/res/values-cs/strings.xml
index e90c215..26affbb 100644
--- a/mediarouter/mediarouter/src/main/res/values-cs/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-cs/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Odeslat. Propojeno"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Odeslat do zařízení"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Hledání zařízení"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Hledání zařízení…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Odpojit"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Zastavit odesílání"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Zavřít"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Přehrávání ve skupině"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nejsou k dispozici žádné informace"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nejsou k dispozici žádná zařízení"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Další informace"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Podívejte se, jak na to"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-da/strings.xml b/mediarouter/mediarouter/src/main/res/values-da/strings.xml
index 33ff77c..8364692 100644
--- a/mediarouter/mediarouter/src/main/res/values-da/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-da/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast. Forbundet"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Cast til"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Finder enheder"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Søger efter enheder…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Afbryd forbindelse"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Stop cast"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Luk"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Afspil på en gruppe"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Der er ingen tilgængelige oplysninger"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Der er ingen tilgængelige enheder"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Få flere oplysninger"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Sådan caster du indhold"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-de/strings.xml b/mediarouter/mediarouter/src/main/res/values-de/strings.xml
index 81687cc..b72ce7a 100644
--- a/mediarouter/mediarouter/src/main/res/values-de/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-de/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Streamen. Verbunden."</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Streamen auf"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Geräte werden gesucht"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Suche nach Geräten läuft…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Trennen"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Streaming beenden"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Schließen"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Auf einem Gruppengerät abspielen"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Keine Informationen verfügbar"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Keine Geräte verfügbar"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Weitere Informationen"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Informationen zum Streamen"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-el/strings.xml b/mediarouter/mediarouter/src/main/res/values-el/strings.xml
index a7c1961..8311a7f 100644
--- a/mediarouter/mediarouter/src/main/res/values-el/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-el/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Μετάδοση. Συνδέθηκε"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Μετάδοση σε"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Εύρεση συσκευών"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Αναζήτηση συσκευών…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Αποσύνδεση"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Διακοπή μετάδοσης"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Κλείσιμο"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Αναπαραγωγή σε κάποια ομάδα"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Δεν υπάρχουν διαθέσιμες πληροφορίες"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Δεν υπάρχουν διαθέσιμες συσκευές"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Μάθετε περισσότερα"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Μάθετε πώς μπορείτε να κάνετε μετάδοση"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-en-rAU/strings.xml b/mediarouter/mediarouter/src/main/res/values-en-rAU/strings.xml
index 2e5a3d7..c402d8c 100644
--- a/mediarouter/mediarouter/src/main/res/values-en-rAU/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-en-rAU/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast. Connected"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Cast to"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Finding devices"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Looking for devices..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Disconnect"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Stop casting"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Close"</string>
@@ -42,4 +43,12 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Play on a group"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"No info available"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"No devices available"</string>
+    <string name="mr_chooser_wifi_warning_description_phone" msgid="2555886884770958244">"Make sure that the other device is on the same Wi-Fi network as this phone"</string>
+    <string name="mr_chooser_wifi_warning_description_tablet" msgid="6038748488793588164">"Make sure that the other device is on the same Wi-Fi network as this tablet"</string>
+    <string name="mr_chooser_wifi_warning_description_tv" msgid="5845921667085074878">"Make sure that the other device is on the same Wi-Fi network as this TV"</string>
+    <string name="mr_chooser_wifi_warning_description_watch" msgid="5255021372884233706">"Make sure that the other device is on the same Wi-Fi network as this watch"</string>
+    <string name="mr_chooser_wifi_warning_description_car" msgid="2998902945608081567">"Make sure that the other device is on the same Wi-Fi network as this car"</string>
+    <string name="mr_chooser_wifi_warning_description_unknown" msgid="3459891599800041449">"Make sure that the other device is on the same Wi-Fi network as this device"</string>
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Learn more"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Learn how to cast"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-en-rCA/strings.xml b/mediarouter/mediarouter/src/main/res/values-en-rCA/strings.xml
index 2e5a3d7..c303499 100644
--- a/mediarouter/mediarouter/src/main/res/values-en-rCA/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-en-rCA/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast. Connected"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Cast to"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Finding devices"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Looking for devices..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Disconnect"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Stop casting"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Close"</string>
@@ -42,4 +43,12 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Play on a group"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"No info available"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"No devices available"</string>
+    <string name="mr_chooser_wifi_warning_description_phone" msgid="2555886884770958244">"Make sure the other device is on the same Wi-Fi network as this phone"</string>
+    <string name="mr_chooser_wifi_warning_description_tablet" msgid="6038748488793588164">"Make sure the other device is on the same Wi-Fi network as this tablet"</string>
+    <string name="mr_chooser_wifi_warning_description_tv" msgid="5845921667085074878">"Make sure the other device is on the same Wi-Fi network as this tv"</string>
+    <string name="mr_chooser_wifi_warning_description_watch" msgid="5255021372884233706">"Make sure the other device is on the same Wi-Fi network as this watch"</string>
+    <string name="mr_chooser_wifi_warning_description_car" msgid="2998902945608081567">"Make sure the other device is on the same Wi-Fi network as this car"</string>
+    <string name="mr_chooser_wifi_warning_description_unknown" msgid="3459891599800041449">"Make sure the other device is on the same Wi-Fi network as this device"</string>
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Learn more"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Learn how to cast"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-en-rGB/strings.xml b/mediarouter/mediarouter/src/main/res/values-en-rGB/strings.xml
index 2e5a3d7..c402d8c 100644
--- a/mediarouter/mediarouter/src/main/res/values-en-rGB/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-en-rGB/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast. Connected"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Cast to"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Finding devices"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Looking for devices..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Disconnect"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Stop casting"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Close"</string>
@@ -42,4 +43,12 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Play on a group"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"No info available"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"No devices available"</string>
+    <string name="mr_chooser_wifi_warning_description_phone" msgid="2555886884770958244">"Make sure that the other device is on the same Wi-Fi network as this phone"</string>
+    <string name="mr_chooser_wifi_warning_description_tablet" msgid="6038748488793588164">"Make sure that the other device is on the same Wi-Fi network as this tablet"</string>
+    <string name="mr_chooser_wifi_warning_description_tv" msgid="5845921667085074878">"Make sure that the other device is on the same Wi-Fi network as this TV"</string>
+    <string name="mr_chooser_wifi_warning_description_watch" msgid="5255021372884233706">"Make sure that the other device is on the same Wi-Fi network as this watch"</string>
+    <string name="mr_chooser_wifi_warning_description_car" msgid="2998902945608081567">"Make sure that the other device is on the same Wi-Fi network as this car"</string>
+    <string name="mr_chooser_wifi_warning_description_unknown" msgid="3459891599800041449">"Make sure that the other device is on the same Wi-Fi network as this device"</string>
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Learn more"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Learn how to cast"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-en-rIN/strings.xml b/mediarouter/mediarouter/src/main/res/values-en-rIN/strings.xml
index 2e5a3d7..c402d8c 100644
--- a/mediarouter/mediarouter/src/main/res/values-en-rIN/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-en-rIN/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast. Connected"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Cast to"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Finding devices"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Looking for devices..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Disconnect"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Stop casting"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Close"</string>
@@ -42,4 +43,12 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Play on a group"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"No info available"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"No devices available"</string>
+    <string name="mr_chooser_wifi_warning_description_phone" msgid="2555886884770958244">"Make sure that the other device is on the same Wi-Fi network as this phone"</string>
+    <string name="mr_chooser_wifi_warning_description_tablet" msgid="6038748488793588164">"Make sure that the other device is on the same Wi-Fi network as this tablet"</string>
+    <string name="mr_chooser_wifi_warning_description_tv" msgid="5845921667085074878">"Make sure that the other device is on the same Wi-Fi network as this TV"</string>
+    <string name="mr_chooser_wifi_warning_description_watch" msgid="5255021372884233706">"Make sure that the other device is on the same Wi-Fi network as this watch"</string>
+    <string name="mr_chooser_wifi_warning_description_car" msgid="2998902945608081567">"Make sure that the other device is on the same Wi-Fi network as this car"</string>
+    <string name="mr_chooser_wifi_warning_description_unknown" msgid="3459891599800041449">"Make sure that the other device is on the same Wi-Fi network as this device"</string>
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Learn more"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Learn how to cast"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-en-rXC/strings.xml b/mediarouter/mediarouter/src/main/res/values-en-rXC/strings.xml
index a95c66a..d155ad0 100644
--- a/mediarouter/mediarouter/src/main/res/values-en-rXC/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-en-rXC/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‎‎Cast. Connected‎‏‎‎‏‎"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‎Cast to‎‏‎‎‏‎"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎‏‎Finding devices‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‎Looking for devices...‎‏‎‎‏‎"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‏‎‎‏‎Disconnect‎‏‎‎‏‎"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎Stop casting‎‏‎‎‏‎"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎Close‎‏‎‎‏‎"</string>
@@ -42,4 +43,12 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‏‏‎‏‏‏‏‎‎‎Play on a group‎‏‎‎‏‎"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‎‎‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎No info available‎‏‎‎‏‎"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‎No devices available‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_wifi_warning_description_phone" msgid="2555886884770958244">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎Make sure the other device is on the same Wi-Fi network as this phone‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_wifi_warning_description_tablet" msgid="6038748488793588164">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎Make sure the other device is on the same Wi-Fi network as this tablet‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_wifi_warning_description_tv" msgid="5845921667085074878">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎Make sure the other device is on the same Wi-Fi network as this tv‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_wifi_warning_description_watch" msgid="5255021372884233706">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‎‏‎‎Make sure the other device is on the same Wi-Fi network as this watch‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_wifi_warning_description_car" msgid="2998902945608081567">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‏‎Make sure the other device is on the same Wi-Fi network as this car‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_wifi_warning_description_unknown" msgid="3459891599800041449">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎Make sure the other device is on the same Wi-Fi network as this device‎‏‎‎‏‎"</string>
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‎‏‎‎‏‏‎"<a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"‎‏‎‎‏‏‏‎Learn more‎‏‎‎‏‏‎"</a>"‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎Learn how to cast‎‏‎‎‏‎"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-es-rUS/strings.xml b/mediarouter/mediarouter/src/main/res/values-es-rUS/strings.xml
index 939dfd8..55a202f 100644
--- a/mediarouter/mediarouter/src/main/res/values-es-rUS/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-es-rUS/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Transmitir. Conectado"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Transmitir a"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Buscando dispositivos"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Buscando dispositivos…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Desconectar"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Detener transmisión"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Cerrar"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Reproducir en un grupo"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Sin información disponible"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"No hay dispositivos disponibles"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Más información"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Descubre cómo transmitir contenido"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-es/strings.xml b/mediarouter/mediarouter/src/main/res/values-es/strings.xml
index fa76eb1..2c584e0 100644
--- a/mediarouter/mediarouter/src/main/res/values-es/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-es/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Enviar; conectado"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Enviar a"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Buscando dispositivos"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Buscando dispositivos…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Desconectar"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Detener envío"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Cerrar"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Reproducir en un grupo"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"No hay información disponible"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"No hay dispositivos disponibles"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Más información"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Consulta cómo enviar contenido"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-et/strings.xml b/mediarouter/mediarouter/src/main/res/values-et/strings.xml
index 5e931bc..4614887 100644
--- a/mediarouter/mediarouter/src/main/res/values-et/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-et/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Ülekandmine. Ühendatud"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Ülekandmine seadmesse"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Seadmete otsimine"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Seadmete otsimine …"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Katkesta ühendus"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Katkesta ülekandmine"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Sule"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Grupis esitamine"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Teave puudub"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Ühtegi seadet pole saadaval"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Lisateave"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Lisateave ülekandmise kohta"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-eu/strings.xml b/mediarouter/mediarouter/src/main/res/values-eu/strings.xml
index 028fe89..bbd0bf9 100644
--- a/mediarouter/mediarouter/src/main/res/values-eu/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-eu/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Igorpena. Lotuta"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Igorri hona"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Gailuak bilatzen"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Gailuak bilatzen…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Deskonektatu"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Gelditu igorpena"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Itxi"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Erreproduzitu talde batean"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Ez dago informaziorik"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Ez dago gailurik"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Lortu informazio gehiago"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Ikasi edukia igortzen"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-fa/strings.xml b/mediarouter/mediarouter/src/main/res/values-fa/strings.xml
index 70cdbf1..498c994 100644
--- a/mediarouter/mediarouter/src/main/res/values-fa/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-fa/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"پخش محتوا. متصل است"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"پخش محتوا به"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"درحال پیدا کردن دستگاه‌ها"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"درحال جستجوی دستگاه‌ها…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"قطع اتصال"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"توقف پخش محتوا"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"بستن"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"بازی در گروه"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"اطلاعاتی در دسترس نیست"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"دستگاهی موجود نیست"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"بیشتر بدانید"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"آشنایی با نحوه پخش محتوا"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-fi/strings.xml b/mediarouter/mediarouter/src/main/res/values-fi/strings.xml
index b76fa27..48c3433 100644
--- a/mediarouter/mediarouter/src/main/res/values-fi/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-fi/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Striimaa. Yhdistetty"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Katso TV:stä"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Etsitään laitteita"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Haetaan laitteita…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Katkaise yhteys"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Lopeta suoratoisto"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Sulje"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Toista ryhmässä"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Ei tietoja saatavilla"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Ei laitteita käytettävissä"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Lue lisää"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Näin striimaaminen onnistuu"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-fr-rCA/strings.xml b/mediarouter/mediarouter/src/main/res/values-fr-rCA/strings.xml
index 0802269..362d240 100644
--- a/mediarouter/mediarouter/src/main/res/values-fr-rCA/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-fr-rCA/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Diffuser. Connecté"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Diffuser vers"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Recherche d\'appareils en cours…"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Recherche d\'appareils en cours…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Déconnecter"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Arrêter la diffusion"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Fermer"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Faire jouer sur un groupe d\'appareils"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Aucune donnée trouvée"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Aucun appareil à proximité"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"En savoir plus"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Apprendre à diffuser du contenu"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-fr/strings.xml b/mediarouter/mediarouter/src/main/res/values-fr/strings.xml
index 2d206ef..7977c6a 100644
--- a/mediarouter/mediarouter/src/main/res/values-fr/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-fr/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast Connecté"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Caster sur"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Recherche d\'appareils…"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Recherche d\'appareils…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Déconnecter"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Arrêter la diffusion"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Fermer"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Lire sur un groupe d\'appareils"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Aucune information disponible"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Aucun appareil disponible"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"En savoir plus"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Découvrez comment caster"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-gl/strings.xml b/mediarouter/mediarouter/src/main/res/values-gl/strings.xml
index 32b85a5..650543f 100644
--- a/mediarouter/mediarouter/src/main/res/values-gl/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-gl/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Emitir. Conectado"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Emitir en"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Buscando dispositivos"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Buscando dispositivos..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Desconectar"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Deter emisión"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Pechar"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Reproducir nun grupo"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Non hai información dispoñible"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Non hai dispositivos dispoñibles"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Máis información"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Aprende a emitir contido"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-gu/strings.xml b/mediarouter/mediarouter/src/main/res/values-gu/strings.xml
index fa11d3aa..ad3131b 100644
--- a/mediarouter/mediarouter/src/main/res/values-gu/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-gu/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"કાસ્ટ કરો. કનેક્ટેડ"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"આના પર કાસ્ટ કરો"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"ઉપકરણો શોધી રહ્યાં છીએ"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"ડિવાઇસ શોધી રહ્યાં છીએ…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ડિસ્કનેક્ટ કરો"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"કાસ્ટ કરવાનું રોકો"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"બંધ કરો"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"જૂથમાં રમો"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"કોઈ માહિતી ઉપલબ્ધ નથી"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"કોઈ ડિવાઇસ ઉપલબ્ધ નથી"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"વધુ જાણો"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"કાસ્ટ કરવાની રીત જાણો"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-hi/strings.xml b/mediarouter/mediarouter/src/main/res/values-hi/strings.xml
index 7b0245c..c68b54c 100644
--- a/mediarouter/mediarouter/src/main/res/values-hi/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-hi/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"कास्ट करें. कनेक्ट हो गया है"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"इस पर कास्‍ट करें"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"डिवाइस ढूंढे जा रहे हैं"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"डिवाइसों को खोजा जा रहा है.."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"डिसकनेक्ट करें"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"कास्ट करना रोकें"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"बंद करें"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"समूह में खेलें"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"कोई जानकारी मौजूद नहीं है"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"कोई डिवाइस मौजूद नहीं है"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"ज़्यादा जानें"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"कास्ट करने का तरीका जानें"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-hr/strings.xml b/mediarouter/mediarouter/src/main/res/values-hr/strings.xml
index 2abdda7..640e78d 100644
--- a/mediarouter/mediarouter/src/main/res/values-hr/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-hr/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Emitiranje. Povezano"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Emitiranje na"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Traženje uređaja"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Traženje uređaja…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Prekini vezu"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Zaustavi emitiranje"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Zatvori"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Reprodukcija u grupi"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Informacije nisu dostupne"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nema dostupnih uređaja"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Saznajte više"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Saznajte kako emitirati"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-hu/strings.xml b/mediarouter/mediarouter/src/main/res/values-hu/strings.xml
index b11e49f..6a5076c 100644
--- a/mediarouter/mediarouter/src/main/res/values-hu/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-hu/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Átküldés. Csatlakoztatva."</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Átküldés ide:"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Eszközök keresése"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Eszközök keresése…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Leválasztás"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Átküldés leállítása"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Bezárás"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Lejátszás eszközcsoportban"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nincs információ"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nincs rendelkezésre álló eszköz"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"További információ"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"A tartalomátküldés elsajátítása"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-hy/strings.xml b/mediarouter/mediarouter/src/main/res/values-hy/strings.xml
index 88103d5..493be03 100644
--- a/mediarouter/mediarouter/src/main/res/values-hy/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-hy/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Հեռարձակել։ Միացած է"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Ընտրեք սարք"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Սարքերի որոնում"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Սարքերի որոնում…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Անջատել"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Կանգնեցնել հեռարձակումը"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Փակել"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Նվագարկեք սարքերի խմբում"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Հասանելի տեղեկություններ չկան"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Հասանելի սարքեր չկան"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Իմանալ ավելին"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Ինչպես հեռարձակել"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-in/strings.xml b/mediarouter/mediarouter/src/main/res/values-in/strings.xml
index 39eb086..f31eb66 100644
--- a/mediarouter/mediarouter/src/main/res/values-in/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-in/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Transmisikan. Terhubung"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Transmisikan ke"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Mencari perangkat"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Mencari perangkat..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Putuskan koneksi"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Hentikan transmisi"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Tutup"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Putar di grup"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Info tidak tersedia"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Perangkat tidak tersedia"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Pelajari lebih lanjut"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Pelajari cara melakukan transmisi"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-is/strings.xml b/mediarouter/mediarouter/src/main/res/values-is/strings.xml
index 31a1da6..0ad41e7 100644
--- a/mediarouter/mediarouter/src/main/res/values-is/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-is/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Útsending. Tengt"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Senda út í"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Leitað að tækjum"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Leitar að tækjum…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Aftengja"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Stöðva útsendingu"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Loka"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Spila í hóp"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Engar upplýsingar í boði"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Engin tæki í boði"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Nánar"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Kynntu þér hvernig þú varpar"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-it/strings.xml b/mediarouter/mediarouter/src/main/res/values-it/strings.xml
index a192988..d6838d6 100644
--- a/mediarouter/mediarouter/src/main/res/values-it/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-it/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Trasmetti. Connesso"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Trasmetti a"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Ricerca di dispositivi"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Ricerca di dispositivi in corso…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Disconnetti"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Interrompi trasmissione"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Chiudi"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Riproduci su un gruppo"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nessuna informazione disponibile"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nessun dispositivo disponibile"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Scopri di più"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Scopri come trasmettere contenuti"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-iw/strings.xml b/mediarouter/mediarouter/src/main/res/values-iw/strings.xml
index 6cc75c1..c410ed0 100644
--- a/mediarouter/mediarouter/src/main/res/values-iw/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-iw/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"‏העברה (cast). מחובר"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"העברה אל"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"מתבצע חיפוש מכשירים"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"מתבצע חיפוש מכשירים…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ניתוק"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"עצירת העברה"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"סגירה"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"הפעלה בקבוצה"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"אין מידע זמין"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"אין מכשירים זמינים"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"מידע נוסף"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"‏איך להעביר (cast)"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ja/strings.xml b/mediarouter/mediarouter/src/main/res/values-ja/strings.xml
index 2633af8..e5af2b1 100644
--- a/mediarouter/mediarouter/src/main/res/values-ja/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ja/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"キャスト。接続済み"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"キャスト先"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"デバイスを検出しています"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"デバイスの検出中..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"接続を解除"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"キャストを停止"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"閉じる"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"グループで再生"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"情報がありません"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"利用可能なデバイスがありません"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"詳細"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"キャスト方法"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ka/strings.xml b/mediarouter/mediarouter/src/main/res/values-ka/strings.xml
index 8c5785e..04d5f12 100644
--- a/mediarouter/mediarouter/src/main/res/values-ka/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ka/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"ტრანსლირება. დაკავშირებული"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"ტრანსლირება"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"მიმდინარეობს მოწყობილობების მოძიება"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"მიმდინარეობს მოწყობილობების ძიება.."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"გათიშვა"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"ტრანსლირების შეწყვეტა"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"დახურვა"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"ჯგუფში დაკვრა"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"ინფორმაცია მიუწვდომელია"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"ხელმისაწვდომი მოწყობილობები არ არის"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"შეიტყვეთ მეტი"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"ტრანსლირების სწავლა"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-kk/strings.xml b/mediarouter/mediarouter/src/main/res/values-kk/strings.xml
index 9289e01..9e8d9f3 100644
--- a/mediarouter/mediarouter/src/main/res/values-kk/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-kk/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Трансляциялау. Қосылды."</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Келесіге трансляциялау:"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Құрылғылар ізделіп жатыр"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Құрылғыларды іздеу…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Ажырату"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Трансляцияны тоқтату"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Жабу"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Топта ойнату"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Ешқандай ақпарат жоқ"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Ешқандай құрылғы табылмады"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Толық ақпарат"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Трансляциялау жолы"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-km/strings.xml b/mediarouter/mediarouter/src/main/res/values-km/strings.xml
index 3e60051..06beedb 100644
--- a/mediarouter/mediarouter/src/main/res/values-km/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-km/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"បញ្ជូន។ បានភ្ជាប់"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"បញ្ជូន​ទៅ"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"កំពុង​ស្វែងរក​ឧបករណ៍"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"កំពុង​ស្វែងរក​ឧបករណ៍..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ផ្ដាច់"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"ឈប់ភ្ជាប់"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"បិទ"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"លេងនៅ​លើក្រុម"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"មិនមានព័ត៌មានទេ"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"មិន​មាន​ឧបករណ៍​ទេ"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"ស្វែងយល់បន្ថែម"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"ស្វែងយល់ពីរបៀបខាស"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-kn/strings.xml b/mediarouter/mediarouter/src/main/res/values-kn/strings.xml
index 02f32b3..b7f91ed 100644
--- a/mediarouter/mediarouter/src/main/res/values-kn/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-kn/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"ಬಿತ್ತರಿಸಿ. ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"ಇದಕ್ಕೆ ಬಿತ್ತರಿಸಿ"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"ಸಾಧನಗಳನ್ನು ಹುಡುಕಲಾಗುತ್ತಿದೆ"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"ಸಾಧನಗಳನ್ನು ಹುಡುಕಲಾಗುತ್ತಿದೆ..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಿ"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"ಬಿತ್ತರಿಸುವುದನ್ನು ನಿಲ್ಲಿಸಿ"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"ಮುಚ್ಚಿ"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"ಗುಂಪಿನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"ಯಾವುದೇ ಮಾಹಿತಿ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"ಯಾವುದೇ ಸಾಧನಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"ಬಿತ್ತರಿಸುವುದು ಹೇಗೆಂದು ತಿಳಿಯಿರಿ"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ko/strings.xml b/mediarouter/mediarouter/src/main/res/values-ko/strings.xml
index 99a6b3c..a67aa44 100644
--- a/mediarouter/mediarouter/src/main/res/values-ko/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ko/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"전송 버튼입니다. 기기가 연결되었습니다"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"전송 대상"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"기기를 찾는 중"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"기기 검색 중…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"연결 해제"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"전송 중지"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"닫기"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"그룹에서 재생"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"사용할 수 있는 정보 없음"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"사용 가능한 기기 없음"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"자세히 알아보기"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"전송 방법 알아보기"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ky/strings.xml b/mediarouter/mediarouter/src/main/res/values-ky/strings.xml
index 33d626b..b68cc3d 100644
--- a/mediarouter/mediarouter/src/main/res/values-ky/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ky/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Тышкы экранга чыгаруу. Туташты"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Түзмөккө чыгаруу"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Түзмөктөр изделүүдө"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Түзмөктөр изделүүдө..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Ажыратуу"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Сыналгыга чыгарууну токтотуу"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Жабуу"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Топто ойнотуу"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Эч маалымат жок"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Жеткиликтүү түзмөктөр жок"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Кеңири маалымат"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Тышкы экранга чыгарууну үйрөнүү"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-lo/strings.xml b/mediarouter/mediarouter/src/main/res/values-lo/strings.xml
index 6ee278f..979a5ba 100644
--- a/mediarouter/mediarouter/src/main/res/values-lo/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-lo/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"ສົ່ງ​ສັນ​ຍານ. ເຊື່ອມຕໍ່ແລ້ວ"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"ສົ່ງສັນຍານໄປທີ່"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"ກຳລັງຊອກຫາອຸປະກອນ"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"ກຳລັງຊອກຫາອຸປະກອນ..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ຕັດການເຊື່ອມຕໍ່"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"ຢຸດການສົ່ງສັນຍານ"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"ປິດ"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"ຫຼິ້ນຢູ່ກຸ່ມ"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"ບໍ່ມີຂໍ້ມູນ"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"ບໍ່ມີອຸປະກອນທີ່ສາມາດໃຊ້ໄດ້"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"ສຶກສາເພີ່ມເຕີມ"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"ສຶກສາວິທີສົ່ງສັນຍານ"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-lt/strings.xml b/mediarouter/mediarouter/src/main/res/values-lt/strings.xml
index c1dd51e..760c35e 100644
--- a/mediarouter/mediarouter/src/main/res/values-lt/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-lt/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Perduoti. Prisijungta"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Perduoti į"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Randami įrenginiai"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Ieškoma įrenginių..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Atjungti"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Sustabdyti perdavimą"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Uždaryti"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Leidimas grupėje"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nėra jokios informacijos"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nėra jokių pasiekiamų įrenginių"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Sužinokite daugiau"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Sužinokite, kaip perduoti"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-lv/strings.xml b/mediarouter/mediarouter/src/main/res/values-lv/strings.xml
index fe6b05e..7b6f4f2 100644
--- a/mediarouter/mediarouter/src/main/res/values-lv/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-lv/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Apraidīt: izveidots savienojums"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Apraidīt uz"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Notiek ierīču meklēšana"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Notiek ierīču meklēšana…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Atvienot"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Pārtraukt apraidi"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Aizvērt"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Atskaņošana grupā"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nav informācijas"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nav pieejamu ierīču"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Uzzināt vairāk"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Uzziniet, kā veikt apraidi"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-mk/strings.xml b/mediarouter/mediarouter/src/main/res/values-mk/strings.xml
index cd3ad5b..ebc035aa 100644
--- a/mediarouter/mediarouter/src/main/res/values-mk/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-mk/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Емитувајте. Поврзано"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Емитување на"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Се бараат уреди"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Се бараат уреди..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Прекини врска"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Сопри со емитување"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Затвори"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Пуштете на група"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Нема достапни информации"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Нема достапни уреди"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Дознајте повеќе"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Дознајте како да емитувате"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ml/strings.xml b/mediarouter/mediarouter/src/main/res/values-ml/strings.xml
index dfb85d4..f4552ec 100644
--- a/mediarouter/mediarouter/src/main/res/values-ml/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ml/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"കാസ്‌റ്റ് ചെയ്യുക. കണക്റ്റ് ചെയ്തു"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"ഇതിലേക്ക് കാസ്‌റ്റ് ചെയ്യുക"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"ഉപകരണങ്ങൾ കണ്ടെത്തുന്നു"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"ഉപകരണങ്ങൾ തിരയുന്നു..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"വിച്ഛേദിക്കുക"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"കാസ്‌റ്റിംഗ് നിർത്തുക"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"അടയ്ക്കുക"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"ഒരു ഗ്രൂപ്പിൽ പ്ലേ ചെയ്യുക"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"വിവരങ്ങളൊന്നും ലഭ്യമല്ല"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"ഉപകരണങ്ങളൊന്നും ലഭ്യമല്ല"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"കൂടുതലറിയുക"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"എങ്ങനെ കാസ്‌റ്റുചെയ്യണമെന്ന് അറിയുക"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-mn/strings.xml b/mediarouter/mediarouter/src/main/res/values-mn/strings.xml
index 83de22e..d02b1d2 100644
--- a/mediarouter/mediarouter/src/main/res/values-mn/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-mn/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Дамжуулалт. Холбогдсон"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Дараахад дамжуулах"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Төхөөрөмжүүдийг хайж байна"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Төхөөрөмжүүдийг хайж байна…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Салгах"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Дамжуулахыг зогсоох"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Хаах"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Бүлэгт тоглуулах"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Мэдээлэл алга"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Ямар ч боломжтой төхөөрөмж байхгүй"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Нэмэлт мэдээлэл авах"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Хэрхэн дамжуулахыг мэдэж авах"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-mr/strings.xml b/mediarouter/mediarouter/src/main/res/values-mr/strings.xml
index f0712411..585f685 100644
--- a/mediarouter/mediarouter/src/main/res/values-mr/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-mr/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"कास्‍ट करा. कनेक्ट केले आहे"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"यावर कास्ट करा"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"डिव्हाइस शोधत आहे"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"डिव्हाइस शोधत आहे…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"‍डिस्कनेक्ट करा"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"कास्ट करणे थांबवा"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"बंद"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"गटावर प्ले करा"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"कोणतीही माहिती उपलब्ध नाही"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"कोणतीही डिव्हाइस उपलब्ध नाहीत"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"अधिक जाणून घ्या"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"कास्ट कसे करावे हे जाणून घ्या"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ms/strings.xml b/mediarouter/mediarouter/src/main/res/values-ms/strings.xml
index 4e29378..76c4de0 100644
--- a/mediarouter/mediarouter/src/main/res/values-ms/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ms/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Hantar. Disambungkan"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Hantar ke"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Mencari peranti"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Mencari peranti..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Putuskan sambungan"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Berhenti menghantar"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Tutup"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Mainkan pada kumpulan"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Maklumat tidak tersedia"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Tiada peranti tersedia"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Ketahui lebih lanjut"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Ketahui cara menghantar"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-my/strings.xml b/mediarouter/mediarouter/src/main/res/values-my/strings.xml
index 3b3aba7..c8eeaec 100644
--- a/mediarouter/mediarouter/src/main/res/values-my/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-my/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast. ချိတ်ဆက်ထားသည်"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"ဤစက်သို့ ကာစ်လုပ်ရန်"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"စက်များကို ရှာနေသည်"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"စက်များကို ရှာနေသည်..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ချိတ်ဆက်မှု ဖြုတ်ရန်"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"ကာစ် ရပ်ရန်"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"ပိတ်ရန်"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"အုပ်စုလိုက် ဖွင့်ခြင်း"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"အချက်အလက် မရရှိနိုင်ပါ"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"စက်များ မရနိုင်ပါ"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"ပိုမိုလေ့လာရန်"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"ကာစ်လုပ်ပုံကို လေ့လာရန်"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-nb/strings.xml b/mediarouter/mediarouter/src/main/res/values-nb/strings.xml
index 27520d9..2731cef 100644
--- a/mediarouter/mediarouter/src/main/res/values-nb/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-nb/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast. Tilkoblet"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Cast til"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Finner enheter"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Ser etter enheter …"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Koble fra"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Stopp castingen"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Lukk"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Spill i en gruppe"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Ingen informasjon er tilgjengelig"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Ingen enheter er tilgjengelige"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Finn ut mer"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Finn ut hvordan du caster"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ne/strings.xml b/mediarouter/mediarouter/src/main/res/values-ne/strings.xml
index e7372d4..c944a64 100644
--- a/mediarouter/mediarouter/src/main/res/values-ne/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ne/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"कास्ट गर्नुहोस्। कनेक्ट गरियो"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"यसमा Cast गर्नुहोस्"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"यन्त्रहरू पत्ता लगाइँदै छ"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"डिभाइसहरू खोजिँदै छ…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"विच्छेद गर्नुहोस्"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"casting रोक्नुहोस्"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"बन्द गर्नुहोस्"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"कुनै समूहमा प्ले गर्नुहोस्"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"कुनै पनि जानकारी उपलब्ध छैन"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"कुनै पनि डिभाइस उपलब्ध छैन"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"थप जान्नुहोस्"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"कास्ट गर्ने तरिका सिक्नुहोस्"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-nl/strings.xml b/mediarouter/mediarouter/src/main/res/values-nl/strings.xml
index eb68ea9..2c8fcf1 100644
--- a/mediarouter/mediarouter/src/main/res/values-nl/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-nl/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Casten. Verbonden."</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Casten naar"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Apparaten zoeken"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Apparaten zoeken..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Verbinding verbreken"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Casten stoppen"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Sluiten"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Afspelen in een groep"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Geen informatie beschikbaar"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Geen apparaten beschikbaar"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Meer informatie"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Meer informatie over casten"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-or/strings.xml b/mediarouter/mediarouter/src/main/res/values-or/strings.xml
index 5c963d7..54b38d5 100644
--- a/mediarouter/mediarouter/src/main/res/values-or/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-or/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"କାଷ୍ଟ କରନ୍ତୁ। ସଂଯୋଗ ହୋଇଛି"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"ଏଥିରେ କାଷ୍ଟ କରନ୍ତୁ"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"ଡିଭାଇସ୍‍ ଖୋଜାଯାଉଛି"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"ଡିଭାଇସଗୁଡ଼ିକୁ ଖୋଜାଯାଉଛି…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ଡିସ୍‌କନେକ୍ଟ କରନ୍ତୁ"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"କାଷ୍ଟ କରିବା ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"ବନ୍ଦ କରନ୍ତୁ"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"ଏକ ଗ୍ରୁପ୍‌ରେ ପ୍ଲେ କରନ୍ତୁ"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"କୌଣସି ସୂଚନା ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"କୌଣସି ଡିଭାଇସ ଉପଲବ୍ଧ ନାହିଁ"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"ଅଧିକ ଜାଣନ୍ତୁ"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"କିପରି କାଷ୍ଟ କରିବେ ତାହା ଜାଣନ୍ତୁ"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-pa/strings.xml b/mediarouter/mediarouter/src/main/res/values-pa/strings.xml
index e5e715b..b3b1455 100644
--- a/mediarouter/mediarouter/src/main/res/values-pa/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-pa/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"ਕਾਸਟ ਕਰੋ। ਕਨੈਕਟ ਹੈ"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"ਇਸਦੇ ਨਾਲ ਕਾਸਟ ਕਰੋ"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"ਡੀਵਾਈਸ ਲੱਭੇ ਜਾ ਰਹੇ ਹਨ"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"ਡੀਵਾਈਸ ਲੱਭੇ ਜਾ ਰਹੇ ਹਨ..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"ਕਾਸਟ ਕਰਨਾ ਬੰਦ ਕਰੋ"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"ਬੰਦ ਕਰੋ"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"ਕਿਸੇ ਗਰੁੱਪ ਵਿੱਚ ਖੇਡੋ"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"ਕੋਈ ਜਾਣਕਾਰੀ ਉਪਲਬਧ ਨਹੀਂ"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"ਕੋਈ ਡੀਵਾਈਸ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"ਹੋਰ ਜਾਣੋ"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"ਕਾਸਟ ਕਰਨ ਦਾ ਤਰੀਕਾ ਜਾਣੋ"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-pl/strings.xml b/mediarouter/mediarouter/src/main/res/values-pl/strings.xml
index 25a280f..e100acb 100644
--- a/mediarouter/mediarouter/src/main/res/values-pl/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-pl/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Przesyłanie. Połączono."</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Przesyłaj na"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Szukam urządzeń"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Szukam urządzeń…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Odłącz"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Zatrzymaj przesyłanie"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Zamknij"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Odtwórz w grupie"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Brak informacji"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Brak dostępnych urządzeń"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Więcej informacji"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Dowiedz się, jak przesyłać treści"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-pt-rBR/strings.xml b/mediarouter/mediarouter/src/main/res/values-pt-rBR/strings.xml
index 0dbea52..881d168 100644
--- a/mediarouter/mediarouter/src/main/res/values-pt-rBR/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-pt-rBR/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Transmitir. Conectado"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Transmitir para"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Localizando dispositivos"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Procurando dispositivos…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Desconectar"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Parar transmissão"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Fechar"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Reproduzir em um grupo"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nenhuma informação disponível"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nenhum dispositivo disponível"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Saiba mais"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Aprenda a transmitir conteúdo"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-pt-rPT/strings.xml b/mediarouter/mediarouter/src/main/res/values-pt-rPT/strings.xml
index 0f72d38..1ee7c1f 100644
--- a/mediarouter/mediarouter/src/main/res/values-pt-rPT/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-pt-rPT/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Transmitir. Ligado"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Transmitir para"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"A localizar dispositivos..."</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"A procurar dispositivos…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Desligar"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Parar transmissão"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Fechar"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Reproduzir num grupo"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nenhuma informação disponível"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nenhum dispositivo disponível"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Saiba mais"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Saiba como transmitir"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-pt/strings.xml b/mediarouter/mediarouter/src/main/res/values-pt/strings.xml
index 0dbea52..881d168 100644
--- a/mediarouter/mediarouter/src/main/res/values-pt/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-pt/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Transmitir. Conectado"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Transmitir para"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Localizando dispositivos"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Procurando dispositivos…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Desconectar"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Parar transmissão"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Fechar"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Reproduzir em um grupo"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nenhuma informação disponível"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nenhum dispositivo disponível"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Saiba mais"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Aprenda a transmitir conteúdo"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ro/strings.xml b/mediarouter/mediarouter/src/main/res/values-ro/strings.xml
index 7c9ec75..e2d80d1 100644
--- a/mediarouter/mediarouter/src/main/res/values-ro/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ro/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Proiectează. S-a conectat"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Proiectează pe"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Se caută dispozitive"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Se caută dispozitive…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Deconectează"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Nu mai proiecta"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Închide"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Redă într-un grup"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nu sunt informații disponibile"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nu sunt disponibile dispozitive"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Află mai multe"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Află cum să proiectezi"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ru/strings.xml b/mediarouter/mediarouter/src/main/res/values-ru/strings.xml
index d9b549d..19330fa 100644
--- a/mediarouter/mediarouter/src/main/res/values-ru/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ru/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Транслировать. Подключено"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Транслировать на"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Поиск устройств…"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Поиск устройств…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Отключить"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Остановить трансляцию"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Закрыть"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Воспроизведение в группе"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Данных нет"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Нет доступных устройств"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Подробнее…"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Как начать трансляцию"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-si/strings.xml b/mediarouter/mediarouter/src/main/res/values-si/strings.xml
index be41018..7a79cdf 100644
--- a/mediarouter/mediarouter/src/main/res/values-si/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-si/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"විකාශය කරන්න සම්බන්ධයි"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"විකාශය"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"උපාංග සෙවීම"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"උපාංග සඳහා සොයමින්..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"විසන්ධි කරන්න"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"විකාශය නවතන්න"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"වසන්න"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"සමූහයක ධාවනය කරන්න"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"ලබා ගත හැකි තොරතුරු නොමැත"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"කිසිදු උපාංගයක් නොමැත"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"තව දැන ගන්න"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"විකාශය කරන්නේ කෙසේ දැයි දැන ගන්න"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-sk/strings.xml b/mediarouter/mediarouter/src/main/res/values-sk/strings.xml
index 7c6c03b..b767a21 100644
--- a/mediarouter/mediarouter/src/main/res/values-sk/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-sk/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Prenášať. Pripojené"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Prenos do zariadenia"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Hľadajú sa zariadenia"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Hľadajú sa zariadenia…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Odpojiť"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Zastaviť prenos"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Zavrieť"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Prehrávanie v skupine"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nie sú k dispozícii žiadne informácie"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nie sú k dispozícii žiadne zariadenia"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Ďalšie informácie"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Pokyny na prenos"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-sl/strings.xml b/mediarouter/mediarouter/src/main/res/values-sl/strings.xml
index b870841..de636bc 100644
--- a/mediarouter/mediarouter/src/main/res/values-sl/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-sl/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Predvajanje. Povezano"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Predvajanje v napravi:"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Iskanje naprav"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Iskanje naprav …"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Prekini povezavo"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Ustavi predvajanje"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Zapri"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Predvajanje v skupini"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Ni podatkov"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nobena naprava ni na voljo"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Več o tem"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Več o tem, kako predvajati"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-sq/strings.xml b/mediarouter/mediarouter/src/main/res/values-sq/strings.xml
index 22432da..fbd11d6 100644
--- a/mediarouter/mediarouter/src/main/res/values-sq/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-sq/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Transmeto. Lidhur"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Transmeto te"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Po kërkon pajisje"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Po kërkon për pajisje..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Shkëput"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Ndalo transmetimin"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Mbyll"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Luaj në një grup"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Nuk jepet asnjë informacion"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Nuk ofrohet asnjë pajisje"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Mëso më shumë"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Mëso si të transmetosh"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-sr/strings.xml b/mediarouter/mediarouter/src/main/res/values-sr/strings.xml
index feb426d..ce79fb9f 100644
--- a/mediarouter/mediarouter/src/main/res/values-sr/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-sr/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Пребаците. Повезан је"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Пребаците на"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Траже се уређаји"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Траже се уређаји…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Прекини везу"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Заустави пребацивање"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Затвори"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Пуштајте у групи"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Нема доступних информација"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Није доступан ниједан уређај"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Сазнајте више"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Сазнајте како да пребацујете"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-sv/strings.xml b/mediarouter/mediarouter/src/main/res/values-sv/strings.xml
index a8b44fa..96f133f 100644
--- a/mediarouter/mediarouter/src/main/res/values-sv/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-sv/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Casta. Ansluten"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Casta till"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Letar efter enheter"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Söker efter enheter …"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Koppla från"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Sluta casta"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Stäng"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Spela upp på en grupp enheter"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Det finns ingen tillgänglig information"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Inga tillgängliga enheter"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Läs mer"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Så castar du"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-sw/strings.xml b/mediarouter/mediarouter/src/main/res/values-sw/strings.xml
index 2e7c75e..297866c9 100644
--- a/mediarouter/mediarouter/src/main/res/values-sw/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-sw/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Tuma. Imeunganishwa"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Tuma kwenye"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Inatafuta vifaa"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Inatafuta vifaa..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Ondoa"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Acha kutuma"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Funga"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Cheza kwenye kikundi"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Hakuna maelezo yaliyopatikana"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Hakuna vifaa vilivyopatikana"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Pata maelezo zaidi"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Jifunze jinsi ta kutuma"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ta/strings.xml b/mediarouter/mediarouter/src/main/res/values-ta/strings.xml
index 2fde991..1cd78d0 100644
--- a/mediarouter/mediarouter/src/main/res/values-ta/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ta/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"அனுப்பும். இணைக்கப்பட்டது"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"இதற்கு அலைபரப்பு:"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"சாதனங்களைத் தேடுகிறது"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"சாதனங்களைத் தேடுகிறது..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"தொடர்பைத் துண்டி"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"அலைபரப்புவதை நிறுத்து"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"மூடுவதற்கான பட்டன்"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"குழுவில் இயக்கு"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"தகவல் எதுவுமில்லை"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"சாதனங்கள் இல்லை"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"மேலும் அறிக"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"அலைபரப்புவதற்கான வழிமுறையை அறிக"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-te/strings.xml b/mediarouter/mediarouter/src/main/res/values-te/strings.xml
index 7622a4c..a4362ec 100644
--- a/mediarouter/mediarouter/src/main/res/values-te/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-te/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"ప్రసారం చేయండి. కనెక్ట్ అయింది"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"దీనికి ప్రసారం చేయండి"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"పరికరాలను కనుగొంటోంది"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"పరికరాల కోసం వెతుకుతోంది..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"డిస్‌కనెక్ట్ చేయి"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"ప్రసారాన్ని ఆపివేయి"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"మూసివేయి"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"గ్రూప్‌లో ప్లే చేయండి"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"సమాచారం అందుబాటులో లేదు"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"పరికరాలు ఏవీ అందుబాటులో లేవు"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"మరింత తెలుసుకోండి"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"ఎలా ప్రసారం చేయాలో తెలుసుకోండి"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-th/strings.xml b/mediarouter/mediarouter/src/main/res/values-th/strings.xml
index 43390b7..5921a0f 100644
--- a/mediarouter/mediarouter/src/main/res/values-th/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-th/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"แคสต์ เชื่อมต่อแล้ว"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"แคสต์ไปยัง"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"กำลังค้นหาอุปกรณ์"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"กำลังค้นหาอุปกรณ์..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ยกเลิกการเชื่อมต่อ"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"หยุดแคสต์"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"ปิด"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"เล่นในกลุ่ม"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"ไม่มีข้อมูล"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"ไม่มีอุปกรณ์ที่ใช้ได้"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"ดูข้อมูลเพิ่มเติม"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"ดูวิธีแคสต์"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-tl/strings.xml b/mediarouter/mediarouter/src/main/res/values-tl/strings.xml
index e63231a..ef98df1 100644
--- a/mediarouter/mediarouter/src/main/res/values-tl/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-tl/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Cast. Nakakonekta"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"I-cast sa"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Naghahanap ng mga device"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Naghahanap ng mga device..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Idiskonekta"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Ihinto ang pag-cast"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Isara"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"I-play sa isang pangkat"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Walang available na impormasyon"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Walang available na device"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Matuto pa"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Matutunan kung paano mag-cast"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-tr/strings.xml b/mediarouter/mediarouter/src/main/res/values-tr/strings.xml
index c919e99..3bc10c7 100644
--- a/mediarouter/mediarouter/src/main/res/values-tr/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-tr/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Yayınla. Bağlı"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Yayınlanacak yer:"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Cihazlar bulunuyor"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Cihazlar aranıyor..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Bağlantıyı kes"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Yayını durdur"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Kapat"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Bir grupta oynatın"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Bilgi yok"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Kullanılabilir cihaz yok"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Daha fazla bilgi"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Nasıl yayınlayacağınızı öğrenin"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-uk/strings.xml b/mediarouter/mediarouter/src/main/res/values-uk/strings.xml
index a0656db..586c738 100644
--- a/mediarouter/mediarouter/src/main/res/values-uk/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-uk/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Трансляція. Підключено"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Транслювати на…"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Пошук пристроїв"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Пошук пристроїв…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Від’єднати"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Припинити трансляцію"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Закрити"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Увімкнути на групі пристроїв"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Немає інформації"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Немає доступних пристроїв"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Докладніше"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Дізнайтеся, як транслювати контент"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-ur/strings.xml b/mediarouter/mediarouter/src/main/res/values-ur/strings.xml
index f1e0370..cf11910 100644
--- a/mediarouter/mediarouter/src/main/res/values-ur/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ur/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"کاسٹ کریں۔ منسلک کردہ"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"اس میں کاسٹ کریں"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"آلات تلاش کیے جا رہے ہیں"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"آلات تلاش کئے جا رہے ہیں..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"منقطع کریں"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"کاسٹ کرنا بند کریں"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"بند کریں"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"گروپ پر چلائیں"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"کوئی معلومات دستیاب نہیں"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"کوئی آلہ دستیاب نہیں ہے"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"مزید جانیں"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"کاسٹ کرنے کا طریقہ جانیں"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-uz/strings.xml b/mediarouter/mediarouter/src/main/res/values-uz/strings.xml
index 98732ad..04a52f0 100644
--- a/mediarouter/mediarouter/src/main/res/values-uz/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-uz/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Translatsiya qilish. Ulandi"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Bunga translatsiya qilish:"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Qurilmalarni topish"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Qurilmalar qidirilmoqda…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Uzish"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Translatsiyani to‘xtatish"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Yopish"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Guruhda ijro qilish"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Hech narsa topilmadi"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Hech qanday qurilma topilmadi"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Batafsil"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Qanday translatsiya qilinadi"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-vi/strings.xml b/mediarouter/mediarouter/src/main/res/values-vi/strings.xml
index f937b8c..ce47483 100644
--- a/mediarouter/mediarouter/src/main/res/values-vi/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-vi/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Truyền. Đã kết nối"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Truyền tới"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Đang tìm thiết bị"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Đang tìm thiết bị…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Ngắt kết nối"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Dừng truyền"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Đóng"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Phát trên nhóm"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Không có thông tin nào"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Không có thiết bị nào"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Tìm hiểu thêm"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Tìm hiểu cách truyền"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-zh-rCN/strings.xml b/mediarouter/mediarouter/src/main/res/values-zh-rCN/strings.xml
index 3155df2..b2b5a33 100644
--- a/mediarouter/mediarouter/src/main/res/values-zh-rCN/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-zh-rCN/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"投放。已连接"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"投放到"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"正在查找设备"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"正在查找设备…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"断开连接"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"停止投放"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"关闭"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"在一组设备上播放"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"没有任何相关信息"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"没有可用的设备"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"了解详情"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"了解如何投放"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-zh-rHK/strings.xml b/mediarouter/mediarouter/src/main/res/values-zh-rHK/strings.xml
index e37af77..781ae37 100644
--- a/mediarouter/mediarouter/src/main/res/values-zh-rHK/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-zh-rHK/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"投放。已連線"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"投放至"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"正在尋找裝置"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"正在尋找裝置…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"解除連接"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"停止投放"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"閂"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"在群組裝置中播放"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"沒有資料可以提供"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"沒有可用的裝置"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"瞭解詳情"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"瞭解投放方式"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-zh-rTW/strings.xml b/mediarouter/mediarouter/src/main/res/values-zh-rTW/strings.xml
index 16cddde..a6bfa83 100644
--- a/mediarouter/mediarouter/src/main/res/values-zh-rTW/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-zh-rTW/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"投放。已連線"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"投放到"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"正在尋找裝置"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"正在尋找裝置..."</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"中斷連線"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"停止投放"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"關閉"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"在群組上播放"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"沒有可用的資訊"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"找不到裝置"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"瞭解詳情"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"瞭解如何投放"</string>
 </resources>
diff --git a/mediarouter/mediarouter/src/main/res/values-zu/strings.xml b/mediarouter/mediarouter/src/main/res/values-zu/strings.xml
index cc0b025..4d0d8d2 100644
--- a/mediarouter/mediarouter/src/main/res/values-zu/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-zu/strings.xml
@@ -24,6 +24,7 @@
     <string name="mr_cast_button_connected" msgid="6073720094880410356">"Sakaza. Ixhunyiwe"</string>
     <string name="mr_chooser_title" msgid="1419936397646839840">"Sakaza ku-"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"Ithola amadivayisi"</string>
+    <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"Ifuna amadivayisi…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"Nqamula"</string>
     <string name="mr_controller_stop_casting" msgid="804210341192624074">"Misa ukusakaza"</string>
     <string name="mr_controller_close_description" msgid="5684434439232634509">"Vala"</string>
@@ -42,4 +43,18 @@
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"Dlala eqenjini"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"Alukho ulwazi olutholakalayo"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"Ayikho idivayisi etholakalayo"</string>
+    <!-- no translation found for mr_chooser_wifi_warning_description_phone (2555886884770958244) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tablet (6038748488793588164) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_tv (5845921667085074878) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_watch (5255021372884233706) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_car (2998902945608081567) -->
+    <skip />
+    <!-- no translation found for mr_chooser_wifi_warning_description_unknown (3459891599800041449) -->
+    <skip />
+    <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"Funda kabanzi"</a></string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"Funda indlela yokusakaza"</string>
 </resources>
diff --git a/palette/OWNERS b/palette/OWNERS
deleted file mode 100644
index c6d7f64..0000000
--- a/palette/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-chrisbanes@google.com
diff --git a/palette/palette-ktx/OWNERS b/palette/palette-ktx/OWNERS
index 6fd8227..c3bcf4a 100644
--- a/palette/palette-ktx/OWNERS
+++ b/palette/palette-ktx/OWNERS
@@ -1 +1,2 @@
+# Bug component: 461229
 yboyar@google.com
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index 2f81d3b..a3f9364 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -25,6 +25,6 @@
 kotlin.code.style=official
 # Disable docs
 androidx.enableDocumentation=false
-androidx.playground.snapshotBuildId=10295852
+androidx.playground.snapshotBuildId=10339410
 androidx.playground.metalavaBuildId=10338742
 androidx.studio.type=playground
diff --git a/preference/preference-ktx/OWNERS b/preference/preference-ktx/OWNERS
index 6fd8227..87591317 100644
--- a/preference/preference-ktx/OWNERS
+++ b/preference/preference-ktx/OWNERS
@@ -1 +1,2 @@
+# Bug component: 461460
 yboyar@google.com
diff --git a/preference/preference/res/values-ne/strings.xml b/preference/preference/res/values-ne/strings.xml
index 6514c49..12775b4 100644
--- a/preference/preference/res/values-ne/strings.xml
+++ b/preference/preference/res/values-ne/strings.xml
@@ -6,6 +6,6 @@
     <string name="expand_button_title" msgid="2427401033573778270">"उन्नत"</string>
     <string name="summary_collapsed_preference_list" msgid="9167775378838880170">"<xliff:g id="CURRENT_ITEMS">%1$s</xliff:g>, <xliff:g id="ADDED_ITEMS">%2$s</xliff:g>"</string>
     <string name="copy" msgid="6083905920877235314">"प्रतिलिपि गर्नुहोस्"</string>
-    <string name="preference_copied" msgid="6685851473431805375">"\"<xliff:g id="SUMMARY">%1$s</xliff:g>\" लाई क्लिपबोर्डमा प्रतिलिपि गरियो।"</string>
+    <string name="preference_copied" msgid="6685851473431805375">"\"<xliff:g id="SUMMARY">%1$s</xliff:g>\" लाई क्लिपबोर्डमा कपी गरियो।"</string>
     <string name="not_set" msgid="6573031135582639649">"सेट गरिएको छैन"</string>
 </resources>
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/InMemorySdkClassLoaderFactoryTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/InMemorySdkClassLoaderFactoryTest.kt
index 93aec37..1f3762f 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/InMemorySdkClassLoaderFactoryTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/InMemorySdkClassLoaderFactoryTest.kt
@@ -33,38 +33,76 @@
 class InMemorySdkClassLoaderFactoryTest {
 
     private lateinit var factoryUnderTest: InMemorySdkClassLoaderFactory
-    private lateinit var testSdkInfo: LocalSdkConfig
+    private lateinit var singleDexSdkInfo: LocalSdkConfig
+    private lateinit var multipleDexSdkInfo: LocalSdkConfig
 
     @Before
     fun setUp() {
         factoryUnderTest = InMemorySdkClassLoaderFactory.create(
             ApplicationProvider.getApplicationContext()
         )
-        testSdkInfo = LocalSdkConfig(
+        singleDexSdkInfo = LocalSdkConfig(
             packageName = "androidx.privacysandbox.sdkruntime.test.v1",
             dexPaths = listOf("RuntimeEnabledSdks/V1/classes.dex"),
             entryPoint = "androidx.privacysandbox.sdkruntime.test.v1.CompatProvider",
             javaResourcesRoot = "RuntimeEnabledSdks/V1/"
         )
+        multipleDexSdkInfo = LocalSdkConfig(
+            packageName = "androidx.privacysandbox.sdkruntime.test.v1",
+            dexPaths = listOf(
+                "RuntimeEnabledSdks/V1/classes.dex",
+                "RuntimeEnabledSdks/RPackage.dex",
+            ),
+            entryPoint = "androidx.privacysandbox.sdkruntime.test.v1.CompatProvider",
+            javaResourcesRoot = "RuntimeEnabledSdks/V1/"
+        )
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O_MR1)
-    fun createClassLoaderFor_whenApi27_returnClassloader() {
+    fun createClassLoaderFor_whenApi27AndMultipleDex_returnClassloader() {
         val classLoader = factoryUnderTest.createClassLoaderFor(
-            testSdkInfo,
+            multipleDexSdkInfo,
             javaClass.classLoader!!
         )
-        val loadedEntryPointClass = classLoader.loadClass(testSdkInfo.entryPoint)
+        val loadedEntryPointClass = classLoader.loadClass(multipleDexSdkInfo.entryPoint)
+        assertThat(loadedEntryPointClass.classLoader).isEqualTo(classLoader)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun createClassLoaderFor_whenApi26AndSingleDex_returnClassloader() {
+        val classLoader = factoryUnderTest.createClassLoaderFor(
+            singleDexSdkInfo,
+            javaClass.classLoader!!
+        )
+        val loadedEntryPointClass = classLoader.loadClass(singleDexSdkInfo.entryPoint)
         assertThat(loadedEntryPointClass.classLoader).isEqualTo(classLoader)
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O)
-    fun createClassLoaderFor_whenApiPre27_throwsSandboxDisabledException() {
+    fun createClassLoaderFor_whenApiPre27AndMultipleDex_throwsSandboxDisabledException() {
         val ex = assertThrows(LoadSdkCompatException::class.java) {
             factoryUnderTest.createClassLoaderFor(
-                testSdkInfo,
+                multipleDexSdkInfo,
+                javaClass.classLoader!!
+            )
+        }
+
+        assertThat(ex.loadSdkErrorCode)
+            .isEqualTo(LoadSdkCompatException.LOAD_SDK_SDK_SANDBOX_DISABLED)
+        assertThat(ex)
+            .hasMessageThat()
+            .startsWith("Can't use InMemoryDexClassLoader")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+    fun createClassLoaderFor_whenApiPre26AndSingleDex_throwsSandboxDisabledException() {
+        val ex = assertThrows(LoadSdkCompatException::class.java) {
+            factoryUnderTest.createClassLoaderFor(
+                singleDexSdkInfo,
                 javaClass.classLoader!!
             )
         }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
index dceeeff1..a473b05 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
@@ -67,7 +67,8 @@
         )
 
         // Clean extracted SDKs between tests
-        File(context.cacheDir, "RuntimeEnabledSdk").deleteRecursively()
+        val codeCacheDir = File(context.applicationInfo.dataDir, "code_cache")
+        File(codeCacheDir, "RuntimeEnabledSdk").deleteRecursively()
     }
 
     @Test
@@ -144,7 +145,7 @@
 
         assertThrows(LoadSdkCompatException::class.java) {
             sdkLoaderWithLowSpaceMode.loadSdk(testSdkConfig)
-        }.hasMessageThat().isEqualTo("Can't use InMemoryDexClassLoader")
+        }.hasMessageThat().startsWith("Can't use InMemoryDexClassLoader")
     }
 
     @Test
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/LocalSdkFolderProviderTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/LocalSdkFolderProviderTest.kt
index 8e06bf7..a91734c 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/LocalSdkFolderProviderTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/LocalSdkFolderProviderTest.kt
@@ -44,7 +44,9 @@
     fun setUp() {
         context = ApplicationProvider.getApplicationContext()
 
-        sdkRootFolder = File(context.cacheDir, "RuntimeEnabledSdk")
+        val codeCache = File(context.applicationInfo.dataDir, "code_cache")
+
+        sdkRootFolder = File(codeCache, "RuntimeEnabledSdk")
         versionFile = File(sdkRootFolder, "Folder.version")
 
         @Suppress("DEPRECATION")
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/InMemorySdkClassLoaderFactory.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/InMemorySdkClassLoaderFactory.kt
index 2fa4a55..28b5823 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/InMemorySdkClassLoaderFactory.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/InMemorySdkClassLoaderFactory.kt
@@ -28,12 +28,13 @@
 
 /**
  * Loading SDK in memory on API 27+
+ * Also support single DEX SDKs on API 26.
  */
 internal abstract class InMemorySdkClassLoaderFactory : SdkLoader.ClassLoaderFactory {
 
     @RequiresApi(Build.VERSION_CODES.O_MR1)
-    internal class InMemoryImpl(
-        private val assetManager: AssetManager
+    private class Api27Impl(
+        private val assetLoader: AssetLoader
     ) : InMemorySdkClassLoaderFactory() {
 
         @DoNotInline
@@ -44,12 +45,7 @@
             try {
                 val buffers = arrayOfNulls<ByteBuffer>(sdkConfig.dexPaths.size)
                 for (i in sdkConfig.dexPaths.indices) {
-                    assetManager.open(sdkConfig.dexPaths[i]).use { inputStream ->
-                        val byteBuffer = ByteBuffer.allocate(inputStream.available())
-                        Channels.newChannel(inputStream).read(byteBuffer)
-                        byteBuffer.flip()
-                        buffers[i] = byteBuffer
-                    }
+                    buffers[i] = assetLoader.load(sdkConfig.dexPaths[i])
                 }
                 return InMemoryDexClassLoader(buffers, parent)
             } catch (ex: Exception) {
@@ -62,7 +58,36 @@
         }
     }
 
-    internal class FailImpl : InMemorySdkClassLoaderFactory() {
+    @RequiresApi(Build.VERSION_CODES.O)
+    private class Api26Impl(
+        private val assetLoader: AssetLoader
+    ) : InMemorySdkClassLoaderFactory() {
+
+        @DoNotInline
+        override fun createClassLoaderFor(
+            sdkConfig: LocalSdkConfig,
+            parent: ClassLoader
+        ): ClassLoader {
+            if (sdkConfig.dexPaths.size != 1) {
+                throw LoadSdkCompatException(
+                    LoadSdkCompatException.LOAD_SDK_SDK_SANDBOX_DISABLED,
+                    "Can't use InMemoryDexClassLoader - API 26 supports only single DEX"
+                )
+            }
+            try {
+                val byteBuffer = assetLoader.load(sdkConfig.dexPaths[0])
+                return InMemoryDexClassLoader(byteBuffer, parent)
+            } catch (ex: Exception) {
+                throw LoadSdkCompatException(
+                    LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
+                    "Failed to instantiate classloader",
+                    ex
+                )
+            }
+        }
+    }
+
+    private class FailImpl : InMemorySdkClassLoaderFactory() {
         @DoNotInline
         override fun createClassLoaderFor(
             sdkConfig: LocalSdkConfig,
@@ -75,10 +100,25 @@
         }
     }
 
+    private class AssetLoader(
+        private val assetManager: AssetManager
+    ) {
+        fun load(assetName: String): ByteBuffer {
+            return assetManager.open(assetName).use { inputStream ->
+                val byteBuffer = ByteBuffer.allocate(inputStream.available())
+                Channels.newChannel(inputStream).read(byteBuffer)
+                byteBuffer.flip()
+                byteBuffer
+            }
+        }
+    }
+
     companion object {
         fun create(context: Context): InMemorySdkClassLoaderFactory {
             return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
-                InMemoryImpl(context.assets)
+                Api27Impl(AssetLoader(context.assets))
+            } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
+                Api26Impl(AssetLoader(context.assets))
             } else {
                 FailImpl()
             }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/LocalSdkFolderProvider.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/LocalSdkFolderProvider.kt
index 72aaffb..2b9e08e 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/LocalSdkFolderProvider.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/LocalSdkFolderProvider.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.os.Build
+import android.os.Build.VERSION_CODES.LOLLIPOP
 import android.os.Build.VERSION_CODES.TIRAMISU
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
@@ -28,10 +29,9 @@
 import java.io.File
 
 /**
- * Create folders for Local SDKs in ([Context.getCacheDir] / RuntimeEnabledSdk / <packageName>)
- *
+ * Create folders for Local SDKs in ([Context.getCodeCacheDir] / RuntimeEnabledSdk / <packageName>)
  * Store Application update time ([android.content.pm.PackageInfo.lastUpdateTime]) in
- * ([Context.getCacheDir] / RuntimeEnabledSdk / Folder.version) file.
+ * ([Context.getCodeCacheDir] / RuntimeEnabledSdk / Folder.version) file.
  * Remove SDK Folders if Application was updated after folders were created.
  */
 internal class LocalSdkFolderProvider private constructor(
@@ -52,6 +52,7 @@
     companion object {
 
         private const val SDK_ROOT_FOLDER = "RuntimeEnabledSdk"
+        private const val CODE_CACHE_FOLDER = "code_cache"
         private const val VERSION_FILE_NAME = "Folder.version"
 
         /**
@@ -66,7 +67,7 @@
         }
 
         private fun createSdkRootFolder(context: Context): File {
-            val rootFolder = File(context.cacheDir, SDK_ROOT_FOLDER)
+            val rootFolder = getRootFolderPath(context)
             val versionFile = File(rootFolder, VERSION_FILE_NAME)
 
             val sdkRootFolderVersion = readVersion(versionFile)
@@ -89,6 +90,19 @@
             return rootFolder
         }
 
+        private fun getRootFolderPath(context: Context): File {
+            if (Build.VERSION.SDK_INT >= LOLLIPOP) {
+                return File(Api21Impl.codeCacheDir(context), SDK_ROOT_FOLDER)
+            }
+
+            // Emulate code cache folder
+            val dataDir = context.applicationInfo.dataDir
+            val codeCacheDir = File(dataDir, CODE_CACHE_FOLDER)
+            codeCacheDir.mkdir()
+
+            return File(codeCacheDir, SDK_ROOT_FOLDER)
+        }
+
         private fun appLastUpdateTime(context: Context): Long {
             if (Build.VERSION.SDK_INT >= TIRAMISU) {
                 return Api33Impl.getLastUpdateTime(context)
@@ -116,6 +130,13 @@
         }
     }
 
+    @RequiresApi(LOLLIPOP)
+    private object Api21Impl {
+        @DoNotInline
+        fun codeCacheDir(context: Context): File =
+            context.codeCacheDir
+    }
+
     @RequiresApi(TIRAMISU)
     private object Api33Impl {
         @DoNotInline
diff --git a/privacysandbox/ui/OWNERS b/privacysandbox/ui/OWNERS
index a322ae5..a75a347 100644
--- a/privacysandbox/ui/OWNERS
+++ b/privacysandbox/ui/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 1314839
 nator@google.com
 akulakov@google.com
 rafaykamran@google.com
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
index 09afb2d..2598fc9 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
@@ -16,7 +16,6 @@
 
 package androidx.privacysandbox.ui.integration.testapp
 
-import android.content.res.Configuration
 import android.os.Bundle
 import android.os.ext.SdkExtensions
 import android.util.Log
@@ -71,7 +70,7 @@
         mSdkLoaded = true
         val sdkApi = ISdkApi.Stub.asInterface(sandboxedSdk.getInterface())
 
-        mSandboxedSdkView1 = findViewById<SandboxedSdkView>(R.id.rendered_view)
+        mSandboxedSdkView1 = findViewById(R.id.rendered_view)
         mSandboxedSdkView1.addStateChangedListener(StateChangeListener(mSandboxedSdkView1))
         mSandboxedSdkView1.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
             sdkApi.loadAd(/*isWebView=*/ true)
@@ -80,18 +79,14 @@
         mSandboxedSdkView2 = SandboxedSdkView(this@MainActivity)
         mSandboxedSdkView2.addStateChangedListener(StateChangeListener(mSandboxedSdkView2))
         mSandboxedSdkView2.layoutParams = ViewGroup.LayoutParams(200, 200)
-        runOnUiThread(Runnable {
+        runOnUiThread {
             findViewById<LinearLayout>(R.id.ad_layout).addView(mSandboxedSdkView2)
-        })
+        }
         mSandboxedSdkView2.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
             sdkApi.loadAd(/*isWebView=*/ false)
         ))
     }
 
-    override fun onConfigurationChanged(newConfig: Configuration) {
-        super.onConfigurationChanged(newConfig)
-    }
-
     private inner class StateChangeListener(val view: SandboxedSdkView) :
         SandboxedSdkUiSessionStateChangedListener {
         override fun onStateChanged(state: SandboxedSdkUiSessionState) {
@@ -101,12 +96,12 @@
                 val parent = view.parent as ViewGroup
                 val index = parent.indexOfChild(view)
                 val textView = TextView(this@MainActivity)
-                textView.setText(state.throwable.message)
+                textView.text = state.throwable.message
 
-                runOnUiThread(Runnable {
+                runOnUiThread {
                     parent.removeView(view)
                     parent.addView(textView, index)
-                })
+                }
             }
         }
     }
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
index c18db92..26e56df 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -28,9 +28,23 @@
         android:layout_height="match_parent"
         android:orientation="vertical">
 
+        <TextView
+            android:id="@+id/headerTextView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="16dp"
+            android:text="@string/app_name"/>
+
         <androidx.privacysandbox.ui.client.view.SandboxedSdkView
             android:id="@+id/rendered_view"
+            android:background="#FF0000"
             android:layout_width="match_parent"
-            android:layout_height="70dp" />
+            android:layout_marginStart="16dp"
+            android:layout_marginEnd="16dp"
+            android:layout_marginBottom="16dp"
+            android:layout_marginTop="16dp"
+            android:layout_height="400dp" />
     </LinearLayout>
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
index 7842b0a..fdd5e4a 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
@@ -232,7 +232,12 @@
     }
 
     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
-        getChildAt(0)?.layout(left, top, right, bottom)
+        // Child needs to receive coordinates that are relative to the parent.
+        getChildAt(0)?.layout(
+            /* l = */ 0,
+            /* t = */ 0,
+            /* r = */ right - left,
+            /* b = */ bottom - top)
         checkClientOpenSession()
     }
 
diff --git a/profileinstaller/OWNERS b/profileinstaller/OWNERS
index 39b0b2cf..af2acea 100644
--- a/profileinstaller/OWNERS
+++ b/profileinstaller/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 1333524
 seanmcq@google.com
 lelandr@google.com
 rahulrav@google.com
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
index 1f5574b..169f081 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
@@ -246,6 +246,7 @@
     }
 
     @Test
+    @Ignore // b/287517337, b/287477564, b/287366097, b/287085166
     fun prependWithDelayedInvalidation() {
         val items = createItems(startId = 0, count = 90)
         db.getDao().insert(items)
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index d1dc611..7d2bd8b 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -36,6 +36,7 @@
 import androidx.room.compiler.processing.util.asKTypeName
 import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.getDeclaredField
+import androidx.room.compiler.processing.util.getDeclaredMethodByJvmName
 import androidx.room.compiler.processing.util.getField
 import androidx.room.compiler.processing.util.getMethodByJvmName
 import androidx.room.compiler.processing.util.getParameter
@@ -1095,18 +1096,20 @@
     @Test
     fun propertyTargetInPrimaryCtorProperty() {
         runTest(
-            sources = listOf(Source.kotlin(
-                "Foo.kt",
-                """
-            package test
-            class Subject(
-                @MyAnnotation val valField: String,
-                @MyAnnotation var varField: String,
-            )
-            @Target(AnnotationTarget.PROPERTY)
-            annotation class MyAnnotation
-            """.trimIndent()
-            )),
+            sources = listOf(
+                Source.kotlin(
+                    "Foo.kt",
+                    """
+                    package test
+                    class Subject(
+                        @MyAnnotation val valField: String,
+                        @MyAnnotation var varField: String,
+                    )
+                    @Target(AnnotationTarget.PROPERTY)
+                    annotation class MyAnnotation
+                    """.trimIndent()
+                )
+            ),
         ) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("test.Subject")
             val myAnnotation = invocation.processingEnv.requireTypeElement("test.MyAnnotation")
@@ -1142,15 +1145,36 @@
             """
             package foo.bar
 
-            interface MyInterface
-            open class Base
+            interface Foo<T>
+            open class FooImpl<T>
+            class Bar
 
             @Target(AnnotationTarget.TYPE)
             annotation class A
+            @Target(
+                AnnotationTarget.FUNCTION,
+                AnnotationTarget.FIELD,
+                AnnotationTarget.CONSTRUCTOR,
+                AnnotationTarget.VALUE_PARAMETER,
+                AnnotationTarget.TYPE
+            )
+            annotation class B
+            @Target(
+                AnnotationTarget.FUNCTION,
+                AnnotationTarget.FIELD,
+                AnnotationTarget.CONSTRUCTOR,
+                AnnotationTarget.VALUE_PARAMETER,
+            )
+            annotation class C
+            annotation class D
 
-            class Subject(i: @A MyInterface) : @A Base(), @A MyInterface {
-                val p: @A MyInterface = TODO()
-                fun f(a: @A MyInterface): @A MyInterface = TODO()
+            class Subject @B @C @D constructor(
+                @B @C @D param: @A @B Foo<@A @B Bar>
+            ) : @A @B FooImpl<@A @B Bar>(), @A @B Foo<@A @B Bar> {
+                @B @C @D val field: @A @B Foo<@A @B Bar> = TODO()
+                @B @C @D fun method(
+                    @B @C @D param: @A @B Foo<@A @B Bar>
+                ): @A @B Foo<@A @B Bar> = TODO()
             }
             """.trimIndent()
         )
@@ -1162,16 +1186,33 @@
             import java.lang.annotation.Target;
             import java.lang.annotation.Repeatable;
 
-            interface MyInterface {}
-            class Base {}
+            interface Foo<T> {}
+            class FooImpl<T> {}
+            class Bar {}
 
             @Target(ElementType.TYPE_USE)
             @interface A {}
+            @Target({
+                ElementType.METHOD,
+                ElementType.FIELD,
+                ElementType.CONSTRUCTOR,
+                ElementType.PARAMETER,
+                ElementType.TYPE_USE
+            })
+            @interface B {}
+            @Target({
+                ElementType.METHOD,
+                ElementType.FIELD,
+                ElementType.CONSTRUCTOR,
+                ElementType.PARAMETER,
+            })
+            @interface C {}
+            @interface D {}
 
-            class Subject extends @A Base implements @A MyInterface {
-                Subject(@A MyInterface i) {}
-                @A MyInterface p;
-                @A MyInterface f(@A MyInterface a) {
+            class Subject extends @A @B FooImpl<@A @B Bar> implements @A @B Foo<@A @B Bar> {
+                @A @B @C @D Foo<@A @B Bar> field;
+                @B @C @D Subject(@A @B @C @D Foo<@A @B Bar> param) {}
+                @A @B @C @D Foo<@A @B Bar> method(@A @B @C @D Foo<@A @B Bar> param) {
                     throw new RuntimeException();
                 }
             }
@@ -1182,38 +1223,113 @@
             runTest(
                 sources = listOf(source)
             ) { invocation ->
-                // We can't see type annotations from precompiled Java classes. Skipping it for now:
-                // https://github.com/google/ksp/issues/1296
-                if (source == javaSource && preCompiled) {
-                    return@runTest
+                fun XAnnotated.getAllAnnotationTypeElements(): List<XTypeElement> {
+                    return getAllAnnotations().filter {
+                        !it.qualifiedName.contentEquals("org.jetbrains.annotations.NotNull")
+                    }.map { it.typeElement }
                 }
+
                 val subject = invocation.processingEnv.requireTypeElement("foo.bar.Subject")
-                // There's an issue in KSP that prevents us from getting type annotations in
-                // places other than supertypes: https://github.com/google/ksp/issues/1325
-                val annotations = if (invocation.isKsp) {
-                    listOf(
-                        subject.superClass!!.getAllAnnotations().first(),
-                        subject.superInterfaces.first().getAllAnnotations().first(),
-                    )
+                val superClass = subject.superClass!!
+                val superInterface = subject.superInterfaces.single()
+                val field = subject.getDeclaredField("field")
+                val method = subject.getDeclaredMethodByJvmName("method")
+                val constructor = subject.getConstructors().single()
+                val a = invocation.processingEnv.requireTypeElement("foo.bar.A")
+                val b = invocation.processingEnv.requireTypeElement("foo.bar.B")
+                val c = invocation.processingEnv.requireTypeElement("foo.bar.C")
+                val d = invocation.processingEnv.requireTypeElement("foo.bar.D")
+
+                // Check the annotations on the elements
+                if (!invocation.isKsp && source == kotlinSource) {
+                    // KAPT places property annotations without targets on the property, which then
+                    // get put onto the synthetic $annotations method in the KAPT stub.
+                    // Unfortunately, synthetic methods can only be read when processing the source
+                    // so it's missing on precompiled class files:
+                    // https://youtrack.jetbrains.com/issue/KT-34684
+                    // TODO(b/288415136): The following should be fixed for non-precompiled sources
+                    //  where we can get the annotation from the $annotations method in KAPT.
+                    assertThat(field.getAllAnnotationTypeElements()).containsExactly(b, c)
                 } else {
-                    listOf(
-                        subject.superClass!!.getAllAnnotations().first(),
-                        subject.superInterfaces.first().getAllAnnotations().first(),
-                        subject.getDeclaredField("p").type.getAllAnnotations().first(),
-                        subject.getConstructors().first().parameters.first().type
-                            .getAllAnnotations().first(),
-                        subject.getMethodByJvmName("f").returnType
-                            .getAllAnnotations().first(),
-                        subject.getMethodByJvmName("f").parameters.first().type
-                            .getAllAnnotations().first()
-                    )
+                    assertThat(field.getAllAnnotationTypeElements()).containsExactly(b, c, d)
                 }
-                annotations.forEach { annotation ->
-                    assertThat(annotation.qualifiedName).isEqualTo("foo.bar.A")
+                if (invocation.isKsp && source == javaSource && !preCompiled) {
+                    // TODO(b/288413750): The following cases should be fixed in KAPT to match KSP.
+                    //  In particular, type-only annotation, A, should not show up on elements.
+                    assertThat(method.getAllAnnotationTypeElements()).containsExactly(a, b, c, d)
+                } else {
+                    assertThat(method.getAllAnnotationTypeElements()).containsExactly(b, c, d)
                 }
-                assertThat(subject.superClass!!.hasAnnotationWithPackage("foo.bar")).isTrue()
-                subject.superInterfaces.forEach {
-                    assertThat(it.hasAnnotationWithPackage("foo.bar")).isTrue()
+                assertThat(method.parameters.single().getAllAnnotationTypeElements())
+                    .containsExactly(b, c, d)
+                assertThat(constructor.getAllAnnotationTypeElements()).containsExactly(b, c, d)
+                assertThat(constructor.parameters.single().getAllAnnotationTypeElements())
+                    .containsExactly(b, c, d)
+
+                val isKsp = invocation.isKsp
+                val isJavaSource = source == javaSource
+
+                // Check the annotations on the types
+                mapOf(
+                    "superClass" to superClass,
+                    "superInterface" to superInterface,
+                    "field" to field.type,
+                    "methodReturnType" to method.returnType,
+                    "methodParameter" to method.parameters.single().type,
+                    "constructorParameter" to constructor.parameters.single().type,
+                ).forEach { (desc, type) ->
+                    // We can't see type annotations from precompiled Java classes in JAVAC.
+                    //   https://github.com/google/ksp/issues/1296
+                    if ((!isKsp && source == javaSource && preCompiled) ||
+                        // TODO(b/288415954): The following cases should be fixed in KSP to match
+                        //  KAPT by working around https://github.com/google/ksp/issues/1325.
+                        (isKsp && isJavaSource && preCompiled && desc == "superClass") ||
+                        (isKsp && isJavaSource && preCompiled && desc == "superInterface") ||
+                        (isKsp && isJavaSource && preCompiled && desc == "field") ||
+                        (isKsp && isJavaSource && preCompiled && desc == "methodReturnType") ||
+                        (isKsp && isJavaSource && preCompiled && desc == "methodParameter") ||
+                        (isKsp && isJavaSource && preCompiled && desc == "constructorParameter") ||
+                        (isKsp && !preCompiled && desc == "methodReturnType") ||
+                        (isKsp && !preCompiled && desc == "methodParameter") ||
+                        (isKsp && !preCompiled && desc == "field") ||
+                        (isKsp && !preCompiled && desc == "constructorParameter")
+                    ) {
+                        assertWithMessage("%s type: %s", desc, type.toString())
+                            .that(type.getAllAnnotationTypeElements())
+                            .isEmpty()
+                    } else {
+                        assertWithMessage("%s type: %s", desc, type.toString())
+                            .that(type.getAllAnnotationTypeElements())
+                            .containsExactly(a, b)
+                    }
+                }
+
+                // Check the annotations on the type arguments
+                mapOf(
+                    "superClass" to superClass.typeArguments.single(),
+                    "superInterface" to superInterface.typeArguments.single(),
+                    "field" to field.type.typeArguments.single(),
+                    "methodReturnType" to method.returnType.typeArguments.single(),
+                    "methodParameter" to method.parameters.single().type.typeArguments.single(),
+                    "constructorParameter" to
+                        constructor.parameters.single().type.typeArguments.single(),
+                ).forEach { (desc, type) ->
+                    // We can't see type annotations from precompiled Java classes in JAVAC.
+                    //   https://github.com/google/ksp/issues/1296
+                    if ((!isKsp && isJavaSource && preCompiled) ||
+                        // TODO(b/288415954): The following cases should be fixed in KSP to match
+                        //  KAPT by working around https://github.com/google/ksp/issues/1325.
+                        (isKsp && !preCompiled) ||
+                        (isKsp && isJavaSource && preCompiled)
+                    ) {
+                        assertWithMessage("%s type-argument: %s", desc, type.toString())
+                            .that(type.getAllAnnotationTypeElements())
+                            .isEmpty()
+                    } else {
+                        assertWithMessage("%s type-argument: %s", desc, type.toString())
+                            .that(type.getAllAnnotationTypeElements())
+                            .containsExactly(a, b)
+                    }
                 }
             }
         }
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java
index dc47d88..f66a185 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java
@@ -63,6 +63,7 @@
         }
     }
 
+    @Ignore // b/288158153
     @Test
     @SdkSuppress(minSdkVersion = 24)
     public void testMultiWindow_pictureInPicture() {
diff --git a/transition/transition-ktx/OWNERS b/transition/transition-ktx/OWNERS
deleted file mode 100644
index e69de29..0000000
--- a/transition/transition-ktx/OWNERS
+++ /dev/null
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeableV2Test.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeableV2Test.kt
new file mode 100644
index 0000000..9f96a9d
--- /dev/null
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeableV2Test.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.ViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties.HorizontalScrollAxisRange
+import androidx.compose.ui.semantics.SemanticsProperties.VerticalScrollAxisRange
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.dp
+import kotlin.math.absoluteValue
+import org.junit.Rule
+import org.junit.Test
+
+// TODO(b/201009199) Some of these tests may need specific values adjusted when swipeableV2
+// supports property nested scrolling, but the tests should all still be valid.
+@OptIn(ExperimentalWearFoundationApi::class)
+class SwipeableV2Test {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun hasHorizontalScrollSemantics_atMaxValue_whenUnswiped() {
+        val state = SwipeableV2State(false)
+        rule.setContent {
+            SimpleSwipeableV2Box { size ->
+                Modifier
+                    .swipeableV2(
+                        state = state,
+                        orientation = Orientation.Horizontal,
+                    )
+                    .swipeAnchors(
+                        state = state,
+                        possibleValues = setOf(false, true),
+                    ) { value, _ ->
+                        when (value) {
+                            false -> 0f
+                            true -> size.width
+                        }
+                    }
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .assert(hasScrollRangeCloseTo(Orientation.Horizontal, value = 1f, maxValue = 1f))
+            .assert(SemanticsMatcher.keyNotDefined(VerticalScrollAxisRange))
+    }
+
+    @Test
+    fun hasVerticalScrollSemantics_atMaxValue_whenUnswiped() {
+        val state = SwipeableV2State(false)
+        rule.setContent {
+            SimpleSwipeableV2Box { size ->
+                Modifier
+                    .swipeableV2(
+                        state = state,
+                        orientation = Orientation.Vertical,
+                    )
+                    .swipeAnchors(
+                        state = state,
+                        possibleValues = setOf(false, true),
+                    ) { value, _ ->
+                        when (value) {
+                            false -> 0f
+                            true -> size.height
+                        }
+                    }
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .assert(hasScrollRangeCloseTo(Orientation.Vertical, value = 1f, maxValue = 1f))
+            .assert(SemanticsMatcher.keyNotDefined(HorizontalScrollAxisRange))
+    }
+
+    @Test
+    fun hasScrollSemantics_whenSwiped() {
+        val state = SwipeableV2State(false)
+        rule.setContent {
+            SimpleSwipeableV2Box { size ->
+                Modifier
+                    .swipeableV2(
+                        state = state,
+                        orientation = Orientation.Horizontal,
+                    )
+                    .swipeAnchors(
+                        state = state,
+                        possibleValues = setOf(false, true),
+                    ) { value, _ ->
+                        when (value) {
+                            false -> 0f
+                            true -> size.width
+                        }
+                    }
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .performTouchInput {
+                down(centerLeft)
+                moveTo(centerLeft + percentOffset(.25f, 0f))
+            }
+        rule.onNodeWithTag(TEST_TAG)
+            .assert(hasScrollRangeCloseTo(Orientation.Horizontal, value = 0.75f, maxValue = 1f))
+
+        rule.onNodeWithTag(TEST_TAG)
+            .performTouchInput {
+                moveTo(center)
+            }
+        rule.onNodeWithTag(TEST_TAG)
+            .assert(hasScrollRangeCloseTo(Orientation.Horizontal, value = 0.5f, maxValue = 1f))
+    }
+
+    @Test
+    fun hasScrollSemantics_whenSwipedWithReverseDirection() {
+        val state = SwipeableV2State(false)
+        rule.setContent {
+            SimpleSwipeableV2Box { size ->
+                Modifier
+                    .swipeableV2(
+                        state = state,
+                        orientation = Orientation.Horizontal,
+                        reverseDirection = true
+                    )
+                    .swipeAnchors(
+                        state = state,
+                        possibleValues = setOf(false, true),
+                    ) { value, _ ->
+                        when (value) {
+                            false -> 0f
+                            true -> size.width
+                        }
+                    }
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .performTouchInput {
+                down(centerRight)
+                moveTo(centerRight - percentOffset(.25f, 0f))
+            }
+        rule.onNodeWithTag(TEST_TAG)
+            .assert(
+                hasScrollRangeCloseTo(
+                    orientation = Orientation.Horizontal,
+                    value = 0.75f,
+                    maxValue = 1f,
+                    reverseScrolling = true
+                )
+            )
+
+        rule.onNodeWithTag(TEST_TAG)
+            .performTouchInput {
+                moveTo(center)
+            }
+        rule.onNodeWithTag(TEST_TAG)
+            .assert(
+                hasScrollRangeCloseTo(
+                    orientation = Orientation.Horizontal,
+                    value = 0.5f,
+                    maxValue = 1f,
+                    reverseScrolling = true
+                )
+            )
+    }
+
+    @Test
+    fun hasNoScrollSemantics_whenDisabled() {
+        val state = SwipeableV2State(false)
+        rule.setContent {
+            SimpleSwipeableV2Box { size ->
+                Modifier
+                    .swipeableV2(
+                        state = state,
+                        orientation = Orientation.Horizontal,
+                        enabled = false
+                    )
+                    .swipeAnchors(
+                        state = state,
+                        possibleValues = setOf(false, true),
+                    ) { value, _ ->
+                        when (value) {
+                            false -> 0f
+                            true -> size.width
+                        }
+                    }
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .assert(SemanticsMatcher.keyNotDefined(HorizontalScrollAxisRange))
+            .assert(SemanticsMatcher.keyNotDefined(VerticalScrollAxisRange))
+    }
+
+    /**
+     * A square [Box] has the [TEST_TAG] test tag. Touch slop is disabled to make swipe calculations
+     * more exact.
+     */
+    @Composable
+    private fun SimpleSwipeableV2Box(swipeableV2Modifier: (Size) -> Modifier) {
+        val originalViewConfiguration = LocalViewConfiguration.current
+        val viewConfiguration = remember(originalViewConfiguration) {
+            object : ViewConfiguration by originalViewConfiguration {
+                override val touchSlop: Float = 0f
+            }
+        }
+
+        with(LocalDensity.current) {
+            val size = 100.dp
+            val sizePx = size.toPx()
+
+            CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
+                Box(
+                    Modifier
+                        .testTag(TEST_TAG)
+                        .requiredSize(size)
+                        .then(remember { swipeableV2Modifier(Size(sizePx, sizePx)) })
+                )
+            }
+        }
+    }
+
+    /**
+     * Matches either a [HorizontalScrollAxisRange] or [VerticalScrollAxisRange] that has the given
+     * [maxValue] and [reverseScrolling], and a `value` that is within a small threshold of [value].
+     */
+    private fun hasScrollRangeCloseTo(
+        orientation: Orientation,
+        value: Float,
+        maxValue: Float,
+        reverseScrolling: Boolean = false
+    ): SemanticsMatcher = SemanticsMatcher(
+        "has $orientation scroll range [0,$maxValue] with " +
+            "value=$value" + if (reverseScrolling) " (reversed)" else ""
+    ) { node ->
+        val threshold = .1f
+        val property = when (orientation) {
+            Orientation.Horizontal -> HorizontalScrollAxisRange
+            Orientation.Vertical -> VerticalScrollAxisRange
+        }
+        node.config.getOrNull(property)?.let { range ->
+            (range.value() - value).absoluteValue <= threshold &&
+                range.maxValue() == maxValue &&
+                range.reverseScrolling == reverseScrolling
+        } ?: false
+    }
+}
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
index 273c59b..f6e888d 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
@@ -46,6 +46,10 @@
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.InspectorValueInfo
 import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.semantics.ScrollAxisRange
+import androidx.compose.ui.semantics.horizontalScrollAxisRange
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.verticalScrollAxisRange
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
@@ -86,15 +90,51 @@
     enabled: Boolean = true,
     reverseDirection: Boolean = false,
     interactionSource: MutableInteractionSource? = null
-) = draggable(
-    state = state.swipeDraggableState,
-    orientation = orientation,
-    enabled = enabled,
-    interactionSource = interactionSource,
-    reverseDirection = reverseDirection,
-    startDragImmediately = state.isAnimationRunning,
-    onDragStopped = { velocity -> launch { state.settle(velocity) } }
-)
+): Modifier {
+    // Swipeables publish scroll range semantics so they look like they can scroll between values
+    // of 0 and 1, inclusive, so that AndroidComposeView can report a value from its canScroll
+    // methods that correctly tells the system's ScrollDismissLayout whether it should intercept
+    // touch values (see b/199908428). This logic is *not* duplicated in the non-Wear swipeable
+    // because it's a bit of a hack to fix navigation in WearOS. Once swipeable implements proper
+    // nested scrolling, and the two swipeable implementations are merged, this fake scrolling stuff
+    // should be gone anyway. Also note that the regular Android swipe-to-go-back gesture works very
+    // differently than the wear gesture so we don't need this workaround to support it.
+    // TODO(b/201009199): Modifier.swipeableV2 should coordinate with the nested scrolling system.
+    val semantics = if (!enabled) Modifier else Modifier.semantics {
+        // Set a fake scroll range axis so that the AndroidComposeView can correctly report whether
+        // scrolling is supported via canScroll{Horizontally,Vertically}.
+        val range = ScrollAxisRange(
+            value = {
+                // Avoid dividing by 0.
+                if (state.minOffset == state.maxOffset) {
+                    0f
+                } else {
+                    val clampedOffset = (state.offset ?: 0f)
+                        .coerceIn(state.minOffset, state.maxOffset)
+                    // [0f, 1f] representing the fraction between the swipe bounds.
+                    // Return the remaining fraction available to swipe.
+                    (state.maxOffset - clampedOffset) / (state.maxOffset - state.minOffset)
+                }
+            },
+            maxValue = { 1f },
+            reverseScrolling = reverseDirection
+        )
+        when (orientation) {
+            Orientation.Horizontal -> horizontalScrollAxisRange = range
+            Orientation.Vertical -> verticalScrollAxisRange = range
+        }
+    }
+
+    return this.then(semantics).draggable(
+        state = state.swipeDraggableState,
+        orientation = orientation,
+        enabled = enabled,
+        interactionSource = interactionSource,
+        reverseDirection = reverseDirection,
+        startDragImmediately = state.isAnimationRunning,
+        onDragStopped = { velocity -> launch { state.settle(velocity) } }
+    )
+}
 
 /**
  * Define anchor points for a given [SwipeableV2State] based on this node's layout size and update
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index 13baed6..b1dcaca 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -92,8 +92,12 @@
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipBorder chipBorder(optional androidx.compose.foundation.BorderStroke? borderStroke, optional androidx.compose.foundation.BorderStroke? disabledBorderStroke);
     method @androidx.wear.compose.material.ExperimentalWearMaterialApi public androidx.wear.compose.material.ChipColors chipColors(androidx.compose.ui.graphics.painter.Painter backgroundPainter, long contentColor, long secondaryContentColor, long iconColor, androidx.compose.ui.graphics.painter.Painter disabledBackgroundPainter, long disabledContentColor, long disabledSecondaryContentColor, long disabledIconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipColors chipColors(optional long backgroundColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor, optional long disabledBackgroundColor, optional long disabledContentColor, optional long disabledSecondaryContentColor, optional long disabledIconColor);
+    method public float getChipHorizontalPadding();
+    method public float getChipVerticalPadding();
     method public androidx.compose.foundation.layout.PaddingValues getCompactChipContentPadding();
+    method public float getCompactChipHorizontalPadding();
     method public androidx.compose.foundation.layout.PaddingValues getCompactChipTapTargetPadding();
+    method public float getCompactChipVerticalPadding();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method public float getIconSize();
     method public float getLargeIconSize();
@@ -104,8 +108,12 @@
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipColors outlinedChipColors(optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipColors primaryChipColors(optional long backgroundColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipColors secondaryChipColors(optional long backgroundColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
+    property public final float ChipHorizontalPadding;
+    property public final float ChipVerticalPadding;
     property public final androidx.compose.foundation.layout.PaddingValues CompactChipContentPadding;
+    property public final float CompactChipHorizontalPadding;
     property public final androidx.compose.foundation.layout.PaddingValues CompactChipTapTargetPadding;
+    property public final float CompactChipVerticalPadding;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     property public final float IconSize;
     property public final float LargeIconSize;
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index 13baed6..b1dcaca 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -92,8 +92,12 @@
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipBorder chipBorder(optional androidx.compose.foundation.BorderStroke? borderStroke, optional androidx.compose.foundation.BorderStroke? disabledBorderStroke);
     method @androidx.wear.compose.material.ExperimentalWearMaterialApi public androidx.wear.compose.material.ChipColors chipColors(androidx.compose.ui.graphics.painter.Painter backgroundPainter, long contentColor, long secondaryContentColor, long iconColor, androidx.compose.ui.graphics.painter.Painter disabledBackgroundPainter, long disabledContentColor, long disabledSecondaryContentColor, long disabledIconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipColors chipColors(optional long backgroundColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor, optional long disabledBackgroundColor, optional long disabledContentColor, optional long disabledSecondaryContentColor, optional long disabledIconColor);
+    method public float getChipHorizontalPadding();
+    method public float getChipVerticalPadding();
     method public androidx.compose.foundation.layout.PaddingValues getCompactChipContentPadding();
+    method public float getCompactChipHorizontalPadding();
     method public androidx.compose.foundation.layout.PaddingValues getCompactChipTapTargetPadding();
+    method public float getCompactChipVerticalPadding();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method public float getIconSize();
     method public float getLargeIconSize();
@@ -104,8 +108,12 @@
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipColors outlinedChipColors(optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipColors primaryChipColors(optional long backgroundColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material.ChipColors secondaryChipColors(optional long backgroundColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
+    property public final float ChipHorizontalPadding;
+    property public final float ChipVerticalPadding;
     property public final androidx.compose.foundation.layout.PaddingValues CompactChipContentPadding;
+    property public final float CompactChipHorizontalPadding;
     property public final androidx.compose.foundation.layout.PaddingValues CompactChipTapTargetPadding;
+    property public final float CompactChipVerticalPadding;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     property public final float IconSize;
     property public final float LargeIconSize;
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
index 2408507..e7077a8 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
@@ -1077,8 +1077,8 @@
         )
     }
 
-    private val ChipHorizontalPadding = 14.dp
-    private val ChipVerticalPadding = 6.dp
+    public val ChipHorizontalPadding = 14.dp
+    public val ChipVerticalPadding = 6.dp
 
     /**
      * The default content padding used by [Chip]
@@ -1090,8 +1090,8 @@
         bottom = ChipVerticalPadding
     )
 
-    private val CompactChipHorizontalPadding = 12.dp
-    private val CompactChipVerticalPadding = 0.dp
+    public val CompactChipHorizontalPadding = 12.dp
+    public val CompactChipVerticalPadding = 0.dp
 
     /**
      * The default content padding used by [CompactChip]
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index ede8db5..6ad4936 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -11,12 +11,16 @@
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors childButtonColors(optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors filledButtonColors(optional long containerColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors filledTonalButtonColors(optional long containerColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
+    method public float getButtonHorizontalPadding();
+    method public float getButtonVerticalPadding();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method public float getIconSize();
     method public float getLargeIconSize();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors imageBackgroundButtonColors(androidx.compose.ui.graphics.painter.Painter backgroundImagePainter, optional androidx.compose.ui.graphics.Brush backgroundImageScrimBrush, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke outlinedButtonBorder(boolean enabled, optional long borderColor, optional long disabledBorderColor, optional float borderWidth);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors outlinedButtonColors(optional long contentColor, optional long secondaryContentColor, optional long iconColor);
+    property public final float ButtonHorizontalPadding;
+    property public final float ButtonVerticalPadding;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     property public final float IconSize;
     property public final float LargeIconSize;
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index ede8db5..6ad4936 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -11,12 +11,16 @@
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors childButtonColors(optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors filledButtonColors(optional long containerColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors filledTonalButtonColors(optional long containerColor, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
+    method public float getButtonHorizontalPadding();
+    method public float getButtonVerticalPadding();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method public float getIconSize();
     method public float getLargeIconSize();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors imageBackgroundButtonColors(androidx.compose.ui.graphics.painter.Painter backgroundImagePainter, optional androidx.compose.ui.graphics.Brush backgroundImageScrimBrush, optional long contentColor, optional long secondaryContentColor, optional long iconColor);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke outlinedButtonBorder(boolean enabled, optional long borderColor, optional long disabledBorderColor, optional float borderWidth);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors outlinedButtonColors(optional long contentColor, optional long secondaryContentColor, optional long iconColor);
+    property public final float ButtonHorizontalPadding;
+    property public final float ButtonVerticalPadding;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     property public final float IconSize;
     property public final float LargeIconSize;
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SliderScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SliderScreenshotTest.kt
new file mode 100644
index 0000000..bd874d2
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SliderScreenshotTest.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.test
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Star
+import androidx.compose.material.icons.filled.ThumbUp
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.InlineSlider
+import androidx.wear.compose.material3.InlineSliderDefaults
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.SCREENSHOT_GOLDEN_PATH
+import androidx.wear.compose.material3.TEST_TAG
+import androidx.wear.compose.material3.setContentWithTheme
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class SliderScreenshotTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+    @get:Rule
+    val testName = TestName()
+
+    @Test
+    fun inlineslider_not_segmented() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                value = 2f,
+                segmented = false,
+                increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
+                decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
+                onValueChange = { },
+                valueRange = 1f..4f,
+                steps = 2
+            )
+        }
+    }
+
+    @Test
+    fun inlineslider_not_segmented_disabled() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                value = 2f,
+                segmented = false,
+                enabled = false,
+                increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
+                decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
+                onValueChange = { },
+                valueRange = 1f..4f,
+                steps = 2
+            )
+        }
+    }
+
+    @Test
+    fun inlineslider_segmented() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                value = 2f,
+                segmented = true,
+                increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
+                decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
+                onValueChange = { },
+                valueRange = 1f..4f,
+                steps = 2
+            )
+        }
+    }
+
+    @Test
+    fun inlineslider_segmented_disabled() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                value = 2f,
+                segmented = true,
+                enabled = false,
+                increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
+                decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
+                onValueChange = { },
+                valueRange = 1f..4f,
+                steps = 2
+            )
+        }
+    }
+
+    @Test
+    public fun inlineslider_rtl() {
+        verifyScreenshot {
+            CompositionLocalProvider(
+                LocalLayoutDirection provides LayoutDirection.Rtl
+            ) {
+                InlineSlider(
+                    modifier = Modifier.testTag(TEST_TAG),
+                    value = 2f,
+                    increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
+                    decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
+                    onValueChange = { },
+                    valueRange = 1f..4f,
+                    steps = 2
+                )
+            }
+        }
+    }
+
+    @Test
+    public fun inlineslider_with_increase_button_disabled() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                valueRange = 0f..4f,
+                value = 4f,
+                increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
+                decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
+                steps = 3,
+                onValueChange = {}
+            )
+        }
+    }
+
+    @Test
+    public fun inlineslider_with_decrease_button_disabled() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                valueRange = 0f..4f,
+                value = 0f,
+                increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
+                decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
+                steps = 3,
+                onValueChange = {}
+            )
+        }
+    }
+
+    @Test
+    fun inlineslider_segmented_with_custom_colors() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                value = 2f,
+                segmented = true,
+                increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
+                decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
+                colors = InlineSliderDefaults.colors(
+                    containerColor = Color.Green,
+                    buttonIconColor = Color.Yellow,
+                    selectedBarColor = Color.Magenta,
+                    unselectedBarColor = Color.White,
+                    barSeparatorColor = Color.Cyan,
+                    disabledContainerColor = Color.DarkGray,
+                    disabledButtonIconColor = Color.LightGray,
+                    disabledSelectedBarColor = Color.Red,
+                    disabledUnselectedBarColor = Color.Blue,
+                    disabledBarSeparatorColor = Color.Gray
+                ),
+                onValueChange = { },
+                valueRange = 1f..4f,
+                steps = 2
+            )
+        }
+    }
+
+    @Test
+    fun inlineslider_custom_icons() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                value = 2f,
+                onValueChange = { },
+                valueRange = 1f..4f,
+                decreaseIcon = {
+                    Icon(
+                        imageVector = Icons.Default.Star,
+                        contentDescription = ""
+                    )
+                },
+                increaseIcon = {
+                    Icon(
+                        imageVector = Icons.Filled.ThumbUp,
+                        contentDescription = ""
+                    )
+                },
+                steps = 2
+            )
+        }
+    }
+
+    @Test
+    fun inlineslider_segmented_with_custom_colors_disabled() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                value = 2f,
+                enabled = false,
+                segmented = true,
+                increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
+                decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
+                colors = InlineSliderDefaults.colors(
+                    containerColor = Color.Green,
+                    buttonIconColor = Color.Yellow,
+                    selectedBarColor = Color.Magenta,
+                    unselectedBarColor = Color.White,
+                    barSeparatorColor = Color.Cyan,
+                    disabledContainerColor = Color.DarkGray,
+                    disabledButtonIconColor = Color.LightGray,
+                    disabledSelectedBarColor = Color.Red,
+                    disabledUnselectedBarColor = Color.Blue,
+                    disabledBarSeparatorColor = Color.Gray
+                ),
+                onValueChange = { },
+                valueRange = 1f..4f,
+                steps = 2
+            )
+        }
+    }
+
+    @Test
+    fun inlineslider_custom_icons_disabled() {
+        verifyScreenshot {
+            InlineSlider(
+                modifier = Modifier.testTag(TEST_TAG),
+                value = 2f,
+                enabled = false,
+                onValueChange = { },
+                valueRange = 1f..4f,
+                decreaseIcon = {
+                    Icon(
+                        imageVector = Icons.Default.Star,
+                        contentDescription = ""
+                    )
+                },
+                increaseIcon = {
+                    Icon(
+                        imageVector = Icons.Filled.ThumbUp,
+                        contentDescription = ""
+                    )
+                },
+                steps = 2
+            )
+        }
+    }
+
+    private fun verifyScreenshot(
+        content: @Composable () -> Unit
+    ) {
+        rule.setContentWithTheme {
+            Box(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(MaterialTheme.colorScheme.background)
+            ) {
+                content()
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, testName.methodName)
+    }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
index 4322d1f..4653c89 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
@@ -837,8 +837,8 @@
         }
     }
 
-    private val ButtonHorizontalPadding = 14.dp
-    private val ButtonVerticalPadding = 6.dp
+    val ButtonHorizontalPadding = 14.dp
+    val ButtonVerticalPadding = 6.dp
 
     /**
      * The default content padding used by [Button]
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
index ea581ad..59ac577 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
@@ -43,6 +43,7 @@
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.vector.ImageVector
@@ -325,11 +326,15 @@
         selectedBarColor: Color = MaterialTheme.colorScheme.primary,
         unselectedBarColor: Color = MaterialTheme.colorScheme.background.copy(alpha = 0.3f),
         barSeparatorColor: Color = MaterialTheme.colorScheme.primaryDim,
-        disabledContainerColor: Color = containerColor.toDisabledColor(),
+        disabledContainerColor: Color = containerColor.toDisabledColor(
+            disabledAlpha = DisabledBorderAndContainerAlpha
+        ),
         disabledButtonIconColor: Color = buttonIconColor.toDisabledColor(),
         disabledSelectedBarColor: Color = selectedBarColor.toDisabledColor(),
         disabledUnselectedBarColor: Color = unselectedBarColor.toDisabledColor(),
-        disabledBarSeparatorColor: Color = barSeparatorColor.toDisabledColor()
+        disabledBarSeparatorColor: Color = barSeparatorColor.toDisabledColor(
+            disabledAlpha = DisabledBorderAndContainerAlpha
+        )
     ): InlineSliderColors = InlineSliderColors(
         containerColor = containerColor,
         buttonIconColor = buttonIconColor,
@@ -465,7 +470,10 @@
 
 internal fun DrawScope.drawProgressBarSeparator(color: Color, position: Float) {
     drawCircle(
-        color, InlineSliderDefaults.BarSeparatorRadius.toPx(), Offset(position, size.height / 2)
+        color = color,
+        radius = InlineSliderDefaults.BarSeparatorRadius.toPx(),
+        center = Offset(position, size.height / 2),
+        blendMode = BlendMode.Src
     )
 }
 
diff --git a/wear/protolayout/protolayout-renderer/build.gradle b/wear/protolayout/protolayout-renderer/build.gradle
index 1fd14e2..4b86d05 100644
--- a/wear/protolayout/protolayout-renderer/build.gradle
+++ b/wear/protolayout/protolayout-renderer/build.gradle
@@ -35,7 +35,7 @@
     implementation "androidx.concurrent:concurrent-futures:1.1.0"
     implementation("androidx.core:core:1.7.0")
     implementation("androidx.vectordrawable:vectordrawable-seekable:1.0.0-beta01")
-    implementation("androidx.wear:wear:1.3.0-beta01")
+    implementation("androidx.wear:wear:1.3.0-rc01")
 
     testImplementation(libs.mockitoCore4)
     testImplementation(libs.testExtJunit)
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
index 1445157..ae4e55c 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
@@ -657,7 +657,7 @@
         this.mUiContext = config.getUiContext();
         this.mRendererResources = config.getRendererResources();
         this.mResourceResolversProvider = config.getResourceResolversProvider();
-        this.mProtoLayoutTheme = ProtoLayoutThemeImpl.defaultTheme(mUiContext);
+        this.mProtoLayoutTheme = config.getProtoLayoutTheme();
         this.mLoadActionListener = config.getLoadActionListener();
         this.mUiExecutorService = config.getUiExecutorService();
         this.mBgExecutorService = config.getBgExecutorService();
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/TypeBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/TypeBuilders.java
index b4d11c6..ae8a854 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/TypeBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/TypeBuilders.java
@@ -35,6 +35,110 @@
     private TypeBuilders() {}
 
     /**
+     * A type for specifying layout constraints when using {@link StringProp} on a data bindable
+     * layout element.
+     *
+     * @since 1.2
+     */
+    public static final class StringLayoutConstraint {
+        private final TypesProto.StringProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        StringLayoutConstraint(TypesProto.StringProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the text string to use as the pattern for the largest text that can be laid out.
+         * Used to ensure that the layout is of a known size during the layout pass.
+         *
+         * @since 1.2
+         */
+        @NonNull
+        public String getPatternForLayout() {
+            return mImpl.getValueForLayout();
+        }
+
+        /**
+         * Gets angular alignment of the actual content within the space reserved by value.
+         *
+         * @since 1.2
+         */
+        @LayoutElementBuilders.TextAlignment
+        public int getAlignment() {
+            return mImpl.getTextAlignmentForLayoutValue();
+        }
+
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public TypesProto.StringProp toProto() {
+            return mImpl;
+        }
+
+        @NonNull
+        static StringLayoutConstraint fromProto(@NonNull TypesProto.StringProp proto) {
+            return new StringLayoutConstraint(proto, null);
+        }
+
+        /** Builder for {@link StringLayoutConstraint}. */
+        public static final class Builder {
+            private final TypesProto.StringProp.Builder mImpl = TypesProto.StringProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1927567664);
+
+            /**
+             * Creates a new builder for {@link StringLayoutConstraint}.
+             *
+             * @param patternForLayout Sets the text string to use as the pattern for the largest
+             *     text that can be laid out. Used to ensure that the layout is of a known size
+             *     during the layout pass.
+             * @since 1.2
+             */
+            public Builder(@NonNull String patternForLayout) {
+                setValue(patternForLayout);
+            }
+
+            /**
+             * Sets the text string to use as the pattern for the largest text that can be laid out.
+             * Used to ensure that the layout is of a known size during the layout pass.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            private Builder setValue(@NonNull String patternForLayout) {
+                mImpl.setValueForLayout(patternForLayout);
+                mFingerprint.recordPropertyUpdate(3, patternForLayout.hashCode());
+                return this;
+            }
+
+            /**
+             * Sets alignment of the actual text within the space reserved by patternForLayout. If
+             * not specified, defaults to center alignment.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setAlignment(@LayoutElementBuilders.TextAlignment int alignment) {
+                mImpl.setTextAlignmentForLayout(AlignmentProto.TextAlignment.forNumber(alignment));
+                mFingerprint.recordPropertyUpdate(4, alignment);
+                return this;
+            }
+
+            /** Builds an instance of {@link StringLayoutConstraint}. */
+            @NonNull
+            public StringLayoutConstraint build() {
+                return new StringLayoutConstraint(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
      * An int32 type.
      *
      * @since 1.0
@@ -264,110 +368,6 @@
     }
 
     /**
-     * A type for specifying layout constraints when using {@link StringProp} on a data bindable
-     * layout element.
-     *
-     * @since 1.2
-     */
-    public static final class StringLayoutConstraint {
-        private final TypesProto.StringProp mImpl;
-        @Nullable private final Fingerprint mFingerprint;
-
-        StringLayoutConstraint(TypesProto.StringProp impl, @Nullable Fingerprint fingerprint) {
-            this.mImpl = impl;
-            this.mFingerprint = fingerprint;
-        }
-
-        /**
-         * Gets the text string to use as the pattern for the largest text that can be laid out.
-         * Used to ensure that the layout is of a known size during the layout pass.
-         *
-         * @since 1.2
-         */
-        @NonNull
-        public String getPatternForLayout() {
-            return mImpl.getValueForLayout();
-        }
-
-        /**
-         * Gets angular alignment of the actual content within the space reserved by value.
-         *
-         * @since 1.2
-         */
-        @LayoutElementBuilders.TextAlignment
-        public int getAlignment() {
-            return mImpl.getTextAlignmentForLayoutValue();
-        }
-
-        @RestrictTo(Scope.LIBRARY_GROUP)
-        @Nullable
-        public Fingerprint getFingerprint() {
-            return mFingerprint;
-        }
-
-        @RestrictTo(Scope.LIBRARY_GROUP)
-        @NonNull
-        public TypesProto.StringProp toProto() {
-            return mImpl;
-        }
-
-        @NonNull
-        static StringLayoutConstraint fromProto(@NonNull TypesProto.StringProp proto) {
-            return new StringLayoutConstraint(proto, null);
-        }
-
-        /** Builder for {@link StringLayoutConstraint}. */
-        public static final class Builder {
-            private final TypesProto.StringProp.Builder mImpl = TypesProto.StringProp.newBuilder();
-            private final Fingerprint mFingerprint = new Fingerprint(-1927567664);
-
-            /**
-             * Creates a new builder for {@link StringLayoutConstraint}.
-             *
-             * @param patternForLayout Sets the text string to use as the pattern for the largest
-             *     text that can be laid out. Used to ensure that the layout is of a known size
-             *     during the layout pass.
-             * @since 1.2
-             */
-            public Builder(@NonNull String patternForLayout) {
-                setValue(patternForLayout);
-            }
-
-            /**
-             * Sets the text string to use as the pattern for the largest text that can be laid out.
-             * Used to ensure that the layout is of a known size during the layout pass.
-             *
-             * @since 1.2
-             */
-            @NonNull
-            private Builder setValue(@NonNull String patternForLayout) {
-                mImpl.setValueForLayout(patternForLayout);
-                mFingerprint.recordPropertyUpdate(3, patternForLayout.hashCode());
-                return this;
-            }
-
-            /**
-             * Sets alignment of the actual text within the space reserved by patternForLayout. If
-             * not specified, defaults to center alignment.
-             *
-             * @since 1.2
-             */
-            @NonNull
-            public Builder setAlignment(@LayoutElementBuilders.TextAlignment int alignment) {
-                mImpl.setTextAlignmentForLayout(AlignmentProto.TextAlignment.forNumber(alignment));
-                mFingerprint.recordPropertyUpdate(4, alignment);
-                return this;
-            }
-
-            /** Builds an instance of {@link StringLayoutConstraint}. */
-            @NonNull
-            public StringLayoutConstraint build() {
-                return new StringLayoutConstraint(mImpl.build(), mFingerprint);
-            }
-        }
-    }
-
-    /**
      * A float type.
      *
      * @since 1.0
diff --git a/wear/watchface/watchface-client/api/current.txt b/wear/watchface/watchface-client/api/current.txt
index c9d2121..8d7f9fd 100644
--- a/wear/watchface/watchface-client/api/current.txt
+++ b/wear/watchface/watchface-client/api/current.txt
@@ -140,7 +140,7 @@
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.List<androidx.wear.watchface.ContentDescriptionLabel> getContentDescriptionLabels() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public String getInstanceId() throws android.os.RemoteException;
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle() throws android.os.RemoteException;
+    method @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws android.os.RemoteException;
     method public default boolean isComplicationDisplayPolicySupported();
@@ -160,7 +160,7 @@
     property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public abstract java.util.List<androidx.wear.watchface.ContentDescriptionLabel> contentDescriptionLabels;
     property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public abstract String instanceId;
     property public default boolean isRemoteWatchFaceViewHostSupported;
-    property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle overlayStyle;
+    property @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle overlayStyle;
     property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public abstract java.time.Instant previewReferenceInstant;
     property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public abstract androidx.wear.watchface.style.UserStyleSchema userStyleSchema;
     field public static final androidx.wear.watchface.client.InteractiveWatchFaceClient.Companion Companion;
@@ -184,13 +184,13 @@
     method public void onWatchFaceReady();
   }
 
-  public final class OverlayStyle {
-    ctor public OverlayStyle();
-    ctor public OverlayStyle(android.graphics.Color? backgroundColor, android.graphics.Color? foregroundColor);
-    method public android.graphics.Color? getBackgroundColor();
-    method public android.graphics.Color? getForegroundColor();
-    property public final android.graphics.Color? backgroundColor;
-    property public final android.graphics.Color? foregroundColor;
+  @Deprecated public final class OverlayStyle {
+    ctor @Deprecated public OverlayStyle();
+    ctor @Deprecated public OverlayStyle(android.graphics.Color? backgroundColor, android.graphics.Color? foregroundColor);
+    method @Deprecated public android.graphics.Color? getBackgroundColor();
+    method @Deprecated public android.graphics.Color? getForegroundColor();
+    property @Deprecated public final android.graphics.Color? backgroundColor;
+    property @Deprecated public final android.graphics.Color? foregroundColor;
   }
 
   public interface RemoteWatchFaceViewHost extends java.lang.AutoCloseable {
diff --git a/wear/watchface/watchface-client/api/restricted_current.txt b/wear/watchface/watchface-client/api/restricted_current.txt
index c9d2121..8d7f9fd 100644
--- a/wear/watchface/watchface-client/api/restricted_current.txt
+++ b/wear/watchface/watchface-client/api/restricted_current.txt
@@ -140,7 +140,7 @@
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.List<androidx.wear.watchface.ContentDescriptionLabel> getContentDescriptionLabels() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public String getInstanceId() throws android.os.RemoteException;
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle() throws android.os.RemoteException;
+    method @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws android.os.RemoteException;
     method public default boolean isComplicationDisplayPolicySupported();
@@ -160,7 +160,7 @@
     property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public abstract java.util.List<androidx.wear.watchface.ContentDescriptionLabel> contentDescriptionLabels;
     property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public abstract String instanceId;
     property public default boolean isRemoteWatchFaceViewHostSupported;
-    property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle overlayStyle;
+    property @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle overlayStyle;
     property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public abstract java.time.Instant previewReferenceInstant;
     property @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public abstract androidx.wear.watchface.style.UserStyleSchema userStyleSchema;
     field public static final androidx.wear.watchface.client.InteractiveWatchFaceClient.Companion Companion;
@@ -184,13 +184,13 @@
     method public void onWatchFaceReady();
   }
 
-  public final class OverlayStyle {
-    ctor public OverlayStyle();
-    ctor public OverlayStyle(android.graphics.Color? backgroundColor, android.graphics.Color? foregroundColor);
-    method public android.graphics.Color? getBackgroundColor();
-    method public android.graphics.Color? getForegroundColor();
-    property public final android.graphics.Color? backgroundColor;
-    property public final android.graphics.Color? foregroundColor;
+  @Deprecated public final class OverlayStyle {
+    ctor @Deprecated public OverlayStyle();
+    ctor @Deprecated public OverlayStyle(android.graphics.Color? backgroundColor, android.graphics.Color? foregroundColor);
+    method @Deprecated public android.graphics.Color? getBackgroundColor();
+    method @Deprecated public android.graphics.Color? getForegroundColor();
+    property @Deprecated public final android.graphics.Color? backgroundColor;
+    property @Deprecated public final android.graphics.Color? foregroundColor;
   }
 
   public interface RemoteWatchFaceViewHost extends java.lang.AutoCloseable {
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
index 5a84988..d758df7 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt
@@ -211,6 +211,7 @@
     }
 }
 
+@Suppress("Deprecation")
 internal class TestWatchfaceOverlayStyleWatchFaceService(
     testContext: Context,
     private var surfaceHolderOverride: SurfaceHolder,
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index a2a5d4b..6e145da 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -503,6 +503,7 @@
     }
 
     @Test
+    @Suppress("Deprecation")
     fun getInteractiveWatchFaceInstance() {
         val testId = "testId"
         // Create and wait for an interactive instance without capturing a reference to it
@@ -927,6 +928,7 @@
     }
 
     @Test
+    @Suppress("Deprecation")
     public fun isComplicationDisplayPolicySupported() {
         val wallpaperService =
             TestWatchfaceOverlayStyleWatchFaceService(
@@ -959,6 +961,7 @@
     }
 
     @Test
+    @Suppress("Deprecation")
     fun watchfaceOverlayStyle() {
         val wallpaperService =
             TestWatchfaceOverlayStyleWatchFaceService(
@@ -978,6 +981,7 @@
     }
 
     @Test
+    @Suppress("Deprecation")
     fun watchfaceOverlayStyle_after_close() {
         val wallpaperService =
             TestWatchfaceOverlayStyleWatchFaceService(
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
index 58e8831..8eed906 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
@@ -194,13 +194,17 @@
     @get:Throws(RemoteException::class) public val previewReferenceInstant: Instant
 
     /**
-     * The watchface's [OverlayStyle] which configures the system status overlay on Wear 3.0 and
-     * beyond. Note for older watch faces which don't support this, the default value will be
-     * returned.
+     * The watchface's [OverlayStyle] which may be null.
+     *
+     * Note while this plumbing got built, it was never used by the system ui on any platform
+     * and it will be removed.
      */
+    @Deprecated("OverlayStyle will be removed in a future release.")
     @get:Throws(RemoteException::class)
+    @Suppress("Deprecation")
     public val overlayStyle: OverlayStyle
         // Default implementation, overridden below.
+        @Suppress("Deprecation")
         get() = OverlayStyle()
 
     /**
@@ -564,6 +568,8 @@
     override val previewReferenceInstant: Instant
         get() = Instant.ofEpochMilli(iInteractiveWatchFace.previewReferenceTimeMillis)
 
+    @Suppress("Deprecation")
+    @Deprecated("OverlayStyle will be removed in a future release.")
     override val overlayStyle: OverlayStyle
         get() {
             if (iInteractiveWatchFace.apiVersion >= 4) {
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/OverlayStyle.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/OverlayStyle.kt
index 7091065..45a30b07 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/OverlayStyle.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/OverlayStyle.kt
@@ -19,10 +19,12 @@
 import android.graphics.Color
 
 /**
- * This class reflects a snapshot of [androidx.wear.watchface.WatchFace.OverlayStyle], which is used
- * to configure the status overlay rendered by the system on top of the watch face. These settings
- * are applicable from Wear 3.0 and will be ignored on earlier devices.
+ * This class reflects a snapshot of [androidx.wear.watchface.WatchFace.OverlayStyle].
+ *
+ * Note while this plumbing got built, it was never used by the system ui on any platform
+ * and it will be removed.
  */
+@Deprecated("OverlayStyle will be removed in a future release.")
 public class OverlayStyle(
     /**
      * The background color of the status indicator tray. This can be any color, including
@@ -43,6 +45,7 @@
      */
     public constructor() : this(null, null)
 
+    @Suppress("Deprecation")
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (javaClass != other?.javaClass) return false
diff --git a/wear/watchface/watchface/api/current.txt b/wear/watchface/watchface/api/current.txt
index 6feb579..f12046e 100644
--- a/wear/watchface/watchface/api/current.txt
+++ b/wear/watchface/watchface/api/current.txt
@@ -311,7 +311,7 @@
   public final class WatchFace {
     ctor public WatchFace(int watchFaceType, androidx.wear.watchface.Renderer renderer);
     method public androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle getLegacyWatchFaceStyle();
-    method public androidx.wear.watchface.WatchFace.OverlayStyle getOverlayStyle();
+    method @Deprecated public androidx.wear.watchface.WatchFace.OverlayStyle getOverlayStyle();
     method public java.time.Instant? getOverridePreviewReferenceInstant();
     method public androidx.wear.watchface.Renderer getRenderer();
     method public int getWatchFaceType();
@@ -319,12 +319,12 @@
     method public androidx.wear.watchface.WatchFace setComplicationDeniedDialogIntent(android.content.Intent? complicationDeniedDialogIntent);
     method public androidx.wear.watchface.WatchFace setComplicationRationaleDialogIntent(android.content.Intent? complicationRationaleDialogIntent);
     method public androidx.wear.watchface.WatchFace setLegacyWatchFaceStyle(androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle);
-    method public androidx.wear.watchface.WatchFace setOverlayStyle(androidx.wear.watchface.WatchFace.OverlayStyle watchFaceOverlayStyle);
+    method @Deprecated public androidx.wear.watchface.WatchFace setOverlayStyle(androidx.wear.watchface.WatchFace.OverlayStyle watchFaceOverlayStyle);
     method public androidx.wear.watchface.WatchFace setOverridePreviewReferenceInstant(java.time.Instant previewReferenceTimeMillis);
     method public androidx.wear.watchface.WatchFace setTapListener(androidx.wear.watchface.WatchFace.TapListener? tapListener);
     method public void setWatchFaceType(int);
     property public final androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle;
-    property public final androidx.wear.watchface.WatchFace.OverlayStyle overlayStyle;
+    property @Deprecated public final androidx.wear.watchface.WatchFace.OverlayStyle overlayStyle;
     property public final java.time.Instant? overridePreviewReferenceInstant;
     property public final androidx.wear.watchface.Renderer renderer;
     property public final int watchFaceType;
@@ -348,13 +348,13 @@
     property public final int viewProtectionMode;
   }
 
-  public static final class WatchFace.OverlayStyle {
-    ctor public WatchFace.OverlayStyle();
-    ctor public WatchFace.OverlayStyle(android.graphics.Color? backgroundColor, android.graphics.Color? foregroundColor);
-    method public android.graphics.Color? getBackgroundColor();
-    method public android.graphics.Color? getForegroundColor();
-    property public final android.graphics.Color? backgroundColor;
-    property public final android.graphics.Color? foregroundColor;
+  @Deprecated public static final class WatchFace.OverlayStyle {
+    ctor @Deprecated public WatchFace.OverlayStyle();
+    ctor @Deprecated public WatchFace.OverlayStyle(android.graphics.Color? backgroundColor, android.graphics.Color? foregroundColor);
+    method @Deprecated public android.graphics.Color? getBackgroundColor();
+    method @Deprecated public android.graphics.Color? getForegroundColor();
+    property @Deprecated public final android.graphics.Color? backgroundColor;
+    property @Deprecated public final android.graphics.Color? foregroundColor;
   }
 
   public static interface WatchFace.TapListener {
diff --git a/wear/watchface/watchface/api/restricted_current.txt b/wear/watchface/watchface/api/restricted_current.txt
index 6feb579..f12046e 100644
--- a/wear/watchface/watchface/api/restricted_current.txt
+++ b/wear/watchface/watchface/api/restricted_current.txt
@@ -311,7 +311,7 @@
   public final class WatchFace {
     ctor public WatchFace(int watchFaceType, androidx.wear.watchface.Renderer renderer);
     method public androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle getLegacyWatchFaceStyle();
-    method public androidx.wear.watchface.WatchFace.OverlayStyle getOverlayStyle();
+    method @Deprecated public androidx.wear.watchface.WatchFace.OverlayStyle getOverlayStyle();
     method public java.time.Instant? getOverridePreviewReferenceInstant();
     method public androidx.wear.watchface.Renderer getRenderer();
     method public int getWatchFaceType();
@@ -319,12 +319,12 @@
     method public androidx.wear.watchface.WatchFace setComplicationDeniedDialogIntent(android.content.Intent? complicationDeniedDialogIntent);
     method public androidx.wear.watchface.WatchFace setComplicationRationaleDialogIntent(android.content.Intent? complicationRationaleDialogIntent);
     method public androidx.wear.watchface.WatchFace setLegacyWatchFaceStyle(androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle);
-    method public androidx.wear.watchface.WatchFace setOverlayStyle(androidx.wear.watchface.WatchFace.OverlayStyle watchFaceOverlayStyle);
+    method @Deprecated public androidx.wear.watchface.WatchFace setOverlayStyle(androidx.wear.watchface.WatchFace.OverlayStyle watchFaceOverlayStyle);
     method public androidx.wear.watchface.WatchFace setOverridePreviewReferenceInstant(java.time.Instant previewReferenceTimeMillis);
     method public androidx.wear.watchface.WatchFace setTapListener(androidx.wear.watchface.WatchFace.TapListener? tapListener);
     method public void setWatchFaceType(int);
     property public final androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle;
-    property public final androidx.wear.watchface.WatchFace.OverlayStyle overlayStyle;
+    property @Deprecated public final androidx.wear.watchface.WatchFace.OverlayStyle overlayStyle;
     property public final java.time.Instant? overridePreviewReferenceInstant;
     property public final androidx.wear.watchface.Renderer renderer;
     property public final int watchFaceType;
@@ -348,13 +348,13 @@
     property public final int viewProtectionMode;
   }
 
-  public static final class WatchFace.OverlayStyle {
-    ctor public WatchFace.OverlayStyle();
-    ctor public WatchFace.OverlayStyle(android.graphics.Color? backgroundColor, android.graphics.Color? foregroundColor);
-    method public android.graphics.Color? getBackgroundColor();
-    method public android.graphics.Color? getForegroundColor();
-    property public final android.graphics.Color? backgroundColor;
-    property public final android.graphics.Color? foregroundColor;
+  @Deprecated public static final class WatchFace.OverlayStyle {
+    ctor @Deprecated public WatchFace.OverlayStyle();
+    ctor @Deprecated public WatchFace.OverlayStyle(android.graphics.Color? backgroundColor, android.graphics.Color? foregroundColor);
+    method @Deprecated public android.graphics.Color? getBackgroundColor();
+    method @Deprecated public android.graphics.Color? getForegroundColor();
+    property @Deprecated public final android.graphics.Color? backgroundColor;
+    property @Deprecated public final android.graphics.Color? foregroundColor;
   }
 
   public static interface WatchFace.TapListener {
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index edb9c49..7845b66 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -390,9 +390,12 @@
 
     /**
      * This class allows the watch face to configure the status overlay which is rendered by the
-     * system on top of the watch face. These settings are applicable from Wear 3.0 and will be
-     * ignored on earlier devices.
+     * system on top of the watch face.
+     *
+     * Note: While the plumbing was built, the System UI side of the this feature was never
+     * implemented, and this method will be removed.
      */
+    @Deprecated("OverlayStyle will be removed in a future release.")
     public class OverlayStyle(
         /**
          * The background color of the status indicator tray. This can be any color, including
@@ -424,6 +427,7 @@
             if (this === other) return true
             if (javaClass != other?.javaClass) return false
 
+            @Suppress("Deprecation")
             other as OverlayStyle
 
             if (backgroundColor != other.backgroundColor) return false
@@ -453,11 +457,21 @@
         }
     }
 
-    /** The [OverlayStyle] which affects Wear 3.0 devices and beyond. */
+    /**
+     * The [OverlayStyle]. This feature is unimplemented on any platform, and will be
+     * removed.
+     */
+    @Deprecated("OverlayStyle will be removed in a future release.")
+    @Suppress("Deprecation")
     public var overlayStyle: OverlayStyle = OverlayStyle()
         private set
 
-    /** Sets the [OverlayStyle] which affects Wear 3.0 devices and beyond. */
+    /**
+     * Sets the [OverlayStyle] which is ignored because this feature is unimplemented on any
+     * platform, and will be removed.
+     */
+    @Deprecated("OverlayStyle will be removed in a future release.")
+    @Suppress("Deprecation")
     public fun setOverlayStyle(watchFaceOverlayStyle: OverlayStyle): WatchFace = apply {
         this.overlayStyle = watchFaceOverlayStyle
     }
@@ -617,6 +631,7 @@
     private val tapListener = watchface.tapListener
     internal var complicationDeniedDialogIntent = watchface.complicationDeniedDialogIntent
     internal var complicationRationaleDialogIntent = watchface.complicationRationaleDialogIntent
+    @Suppress("Deprecation")
     internal var overlayStyle = watchface.overlayStyle
 
     private var mockTime = MockTime(1.0, 0, Long.MAX_VALUE)
@@ -1136,19 +1151,22 @@
                     RenderParameters(params.renderParametersWireFormat)
                 )
 
-            // Restore previous style & complicationSlots if required.
-            if (params.userStyle != null) {
-                currentUserStyleRepository.updateUserStyle(oldStyle)
-            }
+            // No point in restoring the old style and complication if this is headless.
+            if (!watchState.isHeadless) {
+                // Restore previous style & complicationSlots if required.
+                if (params.userStyle != null) {
+                    currentUserStyleRepository.updateUserStyle(oldStyle)
+                }
 
-            if (params.idAndComplicationDatumWireFormats != null) {
-                val now = getNow()
-                for ((id, complicationData) in oldComplicationData) {
-                    complicationSlotsManager.setComplicationDataUpdateSync(
-                        id,
-                        complicationData,
-                        now
-                    )
+                if (params.idAndComplicationDatumWireFormats != null) {
+                    val now = getNow()
+                    for ((id, complicationData) in oldComplicationData) {
+                        complicationSlotsManager.setComplicationDataUpdateSync(
+                            id,
+                            complicationData,
+                            now
+                        )
+                    }
                 }
             }
 
@@ -1218,18 +1236,21 @@
                     params.complicationSlotId
                 )
 
-                // Restore previous ComplicationData & style if required.
-                if (prevData != null) {
-                    val now = getNow()
-                    complicationSlotsManager.setComplicationDataUpdateSync(
-                        params.complicationSlotId,
-                        prevData,
-                        now
-                    )
-                }
+                // No point in restoring the old style and complication if this is headless.
+                if (!watchState.isHeadless) {
+                    // Restore previous ComplicationData & style if required.
+                    if (prevData != null) {
+                        val now = getNow()
+                        complicationSlotsManager.setComplicationDataUpdateSync(
+                            params.complicationSlotId,
+                            prevData,
+                            now
+                        )
+                    }
 
-                if (newStyle != null) {
-                    currentUserStyleRepository.updateUserStyle(oldStyle)
+                    if (newStyle != null) {
+                        currentUserStyleRepository.updateUserStyle(oldStyle)
+                    }
                 }
 
                 SharedMemoryImage.ashmemWriteImageBundle(complicationBitmap)
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
index 210810d..ee6b328 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
@@ -88,6 +88,7 @@
                 ?: Log.w(TAG, "removeWatchFaceListener ignored due to null engine")
         }
 
+    @Suppress("Deprecation")
     override fun getWatchFaceOverlayStyle(): WatchFaceOverlayStyleWireFormat? =
         aidlMethod(TAG, "getWatchFaceOverlayStyle") {
             WatchFaceService.awaitDeferredWatchFaceThenRunOnUiThread(
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/PhoneTypeHelperTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/PhoneTypeHelperTest.kt
index eaa4585..c67bdca 100644
--- a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/PhoneTypeHelperTest.kt
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/PhoneTypeHelperTest.kt
@@ -21,6 +21,7 @@
 import android.database.Cursor
 import android.database.MatrixCursor
 import android.net.Uri
+import android.provider.Settings
 import androidx.test.core.app.ApplicationProvider
 import androidx.wear.phone.interactions.PhoneTypeHelper.Companion.getPhoneDeviceType
 import com.google.common.truth.Truth.assertThat
@@ -31,6 +32,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
+import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
 import org.robolectric.shadows.ShadowContentResolver
 
@@ -60,6 +62,7 @@
     }
 
     @Test
+    @Config(maxSdk = 28)
     fun testGetDeviceType_returnsIosWhenAltMode() {
         createFakePhoneTypeQuery(PhoneTypeHelper.IOS_MODE)
         assertThat(
@@ -68,6 +71,7 @@
     }
 
     @Test
+    @Config(maxSdk = 28)
     fun testGetDeviceType_returnsAndroidWhenNonAltMode() {
         createFakePhoneTypeQuery(PhoneTypeHelper.ANDROID_MODE)
         assertThat(
@@ -76,6 +80,7 @@
     }
 
     @Test
+    @Config(maxSdk = 28)
     fun testGetDeviceType_returnsErrorWhenModeUnknown() {
         createFakePhoneTypeQuery(PhoneTypeHelper.DEVICE_TYPE_UNKNOWN)
         assertThat(
@@ -84,68 +89,80 @@
     }
 
     @Test
+    @Config(maxSdk = 28)
     fun testGetDeviceType_returnsErrorWhenContentMissing() {
         assertThat(
             getPhoneDeviceType(ApplicationProvider.getApplicationContext())
         ).isEqualTo(PhoneTypeHelper.DEVICE_TYPE_ERROR)
     }
 
-    /*
-    Robolectric currently doesn't support API 30, so these tests will automatically fail if
-    uncommented.
-    TODO(b/178086256): Include these tests in the class when API 30 is supported.
     @Test
-    @Config(sdk = [30])
-    fun testGetDeviceType_returnsErrorWhenContentMissing_onR() {
-        assertEquals(
-            getPhoneDeviceType(ApplicationProvider.getApplicationContext()),
-            PhoneTypeHelper.DEVICE_TYPE_UNKNOWN
-        )
+    @Config(minSdk = 29)
+    fun testGetDeviceType_returnsIosWhenAltMode_fromQ() {
+        createFakePhoneTypeQuery(PhoneTypeHelper.IOS_MODE)
+        Settings.Global.putInt(contentResolver, PhoneTypeHelper.PAIRED_DEVICE_OS_TYPE,
+            PhoneTypeHelper.IOS_MODE)
+        assertThat(
+            getPhoneDeviceType(ApplicationProvider.getApplicationContext())
+        ).isEqualTo(PhoneTypeHelper.DEVICE_TYPE_IOS)
     }
 
+    @Test
+    @Config(minSdk = 29)
+    fun testGetDeviceType_returnsAndroidWhenNonAltMode_fromQ() {
+        Settings.Global.putInt(contentResolver, PhoneTypeHelper.PAIRED_DEVICE_OS_TYPE,
+            PhoneTypeHelper.ANDROID_MODE)
+        assertThat(
+            getPhoneDeviceType(ApplicationProvider.getApplicationContext())
+        ).isEqualTo(PhoneTypeHelper.DEVICE_TYPE_ANDROID)
+    }
 
     @Test
-    @Config(sdk = [30])
-    fun testGetDeviceType_returnsAndroid_onR() {
+    @Config(minSdk = 29)
+    fun testGetDeviceType_returnsErrorWhenContentMissing_fromQ() {
+        assertThat(
+            getPhoneDeviceType(ApplicationProvider.getApplicationContext())
+        ).isEqualTo(PhoneTypeHelper.DEVICE_TYPE_UNKNOWN)
+    }
+
+    @Test
+    @Config(minSdk = 29)
+    fun testGetDeviceType_returnsAndroid_fromQ() {
         Settings.Global.putInt(
             contentResolver,
             PhoneTypeHelper.PAIRED_DEVICE_OS_TYPE,
             PhoneTypeHelper.ANDROID_MODE
         )
-        assertEquals(
-            getPhoneDeviceType(ApplicationProvider.getApplicationContext()),
-            PhoneTypeHelper.DEVICE_TYPE_ANDROID
-        )
+        assertThat(
+            getPhoneDeviceType(ApplicationProvider.getApplicationContext())
+        ).isEqualTo(PhoneTypeHelper.DEVICE_TYPE_ANDROID)
     }
 
     @Test
-    @Config(sdk = [30])
-    fun testGetDeviceType_returnsErrorWhenModeUnknown_onR() {
+    @Config(minSdk = 29)
+    fun testGetDeviceType_returnsErrorWhenModeUnknown_fromQ() {
         Settings.Global.putInt(
             contentResolver,
             PhoneTypeHelper.PAIRED_DEVICE_OS_TYPE,
             PhoneTypeHelper.UNKNOWN_MODE
         )
-        assertEquals(
-            getPhoneDeviceType(ApplicationProvider.getApplicationContext()),
-            PhoneTypeHelper.DEVICE_TYPE_UNKNOWN
-        )
+        assertThat(
+            getPhoneDeviceType(ApplicationProvider.getApplicationContext())
+        ).isEqualTo(PhoneTypeHelper.DEVICE_TYPE_UNKNOWN)
     }
 
     @Test
-    @Config(sdk = [30])
-    fun testGetDeviceType_returnsIos_onR() {
+    @Config(minSdk = 29)
+    fun testGetDeviceType_returnsIos_fromQ() {
         Settings.Global.putInt(
             contentResolver,
             PhoneTypeHelper.PAIRED_DEVICE_OS_TYPE,
             PhoneTypeHelper.IOS_MODE
         )
-        assertEquals(
-            getPhoneDeviceType(ApplicationProvider.getApplicationContext()),
-            PhoneTypeHelper.DEVICE_TYPE_IOS
-        )
+        assertThat(
+            getPhoneDeviceType(ApplicationProvider.getApplicationContext())
+        ).isEqualTo(PhoneTypeHelper.DEVICE_TYPE_IOS)
     }
-    */
 
     companion object {
         private fun createFakeBluetoothModeCursor(bluetoothMode: Int): Cursor {
diff --git a/wear/wear-phone-interactions/src/test/resources/robolectric.properties b/wear/wear-phone-interactions/src/test/resources/robolectric.properties
index 1061465..910ab9d 100644
--- a/wear/wear-phone-interactions/src/test/resources/robolectric.properties
+++ b/wear/wear-phone-interactions/src/test/resources/robolectric.properties
@@ -14,7 +14,5 @@
 # limitations under the License.
 #
 
-# Robolectric currently doesn't support API 31, so we have to explicitly specify the target sdk
-# levels for now.
-# TODO(b/177072877): Remove when no longer necessary.
-sdk=28
+# Temporary until we update Robolectric to support API level 34.
+sdk=33
diff --git a/wear/wear-remote-interactions/src/test/resources/robolectric.properties b/wear/wear-remote-interactions/src/test/resources/robolectric.properties
index 415d731..397c939 100644
--- a/wear/wear-remote-interactions/src/test/resources/robolectric.properties
+++ b/wear/wear-remote-interactions/src/test/resources/robolectric.properties
@@ -14,7 +14,4 @@
 # limitations under the License.
 #
 
-# Robolectric currently doesn't support API 31, so we have to explicitly the target sdk levels for
-# now.
-# TODO(b/177072877): Remove when no longer necessary.
-sdk=25,26,27,28,29,30
+minSdk=25
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
index d50e6b3..676d286 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
@@ -63,7 +63,6 @@
      *     <li>{@link androidx.window.extensions.layout.FoldingFeature} APIs</li>
      *     <li>{@link androidx.window.extensions.layout.WindowLayoutInfo} APIs</li>
      *     <li>{@link androidx.window.extensions.layout.WindowLayoutComponent} APIs</li>
-     *     <li>{@link androidx.window.extensions.area.WindowAreaComponent} APIs</li>
      * </ul>
      * </p>
      */
@@ -100,6 +99,7 @@
      *     <li>{@link ActivityEmbeddingComponent#updateSplitAttributes(IBinder, SplitAttributes)}
      *     </li>
      *     <li>{@link ActivityEmbeddingComponent#finishActivityStacks(Set)}</li>
+     *     <li>{@link androidx.window.extensions.area.WindowAreaComponent} APIs</li>
      * </ul>
      * </p>
      */
diff --git a/window/window-testing/lint-baseline.xml b/window/window-testing/lint-baseline.xml
deleted file mode 100644
index 4225368..0000000
--- a/window/window-testing/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.1.0-alpha07">
-
-    <issue
-        id="BanHideAnnotation"
-        message="@hide is not allowed in Javadoc"
-        errorLine1="val TEST_ACTIVITY_STACK_TOKEN = Binder()"
-        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/testing/embedding/ActivityStackTesting.kt"/>
-    </issue>
-
-</issues>
diff --git a/window/window-testing/src/main/java/androidx/window/testing/embedding/ActivityStackTesting.kt b/window/window-testing/src/main/java/androidx/window/testing/embedding/ActivityStackTesting.kt
index 396936b..879ed68 100644
--- a/window/window-testing/src/main/java/androidx/window/testing/embedding/ActivityStackTesting.kt
+++ b/window/window-testing/src/main/java/androidx/window/testing/embedding/ActivityStackTesting.kt
@@ -42,7 +42,6 @@
     isEmpty: Boolean = false,
 ): ActivityStack = ActivityStack(activitiesInProcess, isEmpty, TEST_ACTIVITY_STACK_TOKEN)
 
-/** @hide */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @VisibleForTesting
 @JvmField
diff --git a/window/window/src/androidTest/java/androidx/window/layout/ContextUtilsTest.kt b/window/window/src/androidTest/java/androidx/window/layout/util/ContextCompatHelperTest.kt
similarity index 84%
rename from window/window/src/androidTest/java/androidx/window/layout/ContextUtilsTest.kt
rename to window/window/src/androidTest/java/androidx/window/layout/util/ContextCompatHelperTest.kt
index 7cb4812..59d0af6 100644
--- a/window/window/src/androidTest/java/androidx/window/layout/ContextUtilsTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/layout/util/ContextCompatHelperTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,26 +14,25 @@
  * limitations under the License.
  */
 
-package androidx.window.layout
+package androidx.window.layout.util
 
 import android.app.Activity
 import android.content.ContextWrapper
 import android.inputmethodservice.InputMethodService
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import androidx.window.layout.util.ContextUtils
-import androidx.window.layout.util.ContextUtils.unwrapUiContext
+import androidx.window.layout.util.ContextCompatHelper.unwrapUiContext
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
 
 /**
- * Instrumentation tests for [ContextUtils].
+ * Instrumentation tests for [ContextCompatHelper].
  */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class ContextUtilsTest {
+class ContextCompatHelperTest {
 
     @Test
     fun testUnwrapUiContext_noContextWrapper_activity() {
diff --git a/window/window/src/main/java/androidx/window/layout/ContextCompatHelper.kt b/window/window/src/main/java/androidx/window/layout/ContextCompatHelper.kt
deleted file mode 100644
index 2bc997d..0000000
--- a/window/window/src/main/java/androidx/window/layout/ContextCompatHelper.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2021 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.window.layout.util
-
-import android.app.Activity
-import android.content.Context
-import android.graphics.Rect
-import android.os.Build
-import android.view.WindowManager
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
-import androidx.annotation.UiContext
-import androidx.core.view.WindowInsetsCompat
-import androidx.window.layout.WindowMetrics
-
-@RequiresApi(Build.VERSION_CODES.N)
-internal object ContextCompatHelperApi24 {
-    fun isInMultiWindowMode(activity: Activity): Boolean {
-        return activity.isInMultiWindowMode
-    }
-}
-
-@RequiresApi(Build.VERSION_CODES.R)
-internal object ContextCompatHelperApi30 {
-
-    fun currentWindowMetrics(@UiContext context: Context): WindowMetrics {
-        val wm = context.getSystemService(WindowManager::class.java)
-        val insets = WindowInsetsCompat.toWindowInsetsCompat(wm.currentWindowMetrics.windowInsets)
-        return WindowMetrics(wm.currentWindowMetrics.bounds, insets)
-    }
-
-    fun currentWindowBounds(@UiContext context: Context): Rect {
-        val wm = context.getSystemService(WindowManager::class.java)
-        return wm.currentWindowMetrics.bounds
-    }
-
-    fun currentWindowInsets(@UiContext context: Context): WindowInsetsCompat {
-        val wm = context.getSystemService(WindowManager::class.java)
-        return WindowInsetsCompat.toWindowInsetsCompat(wm.currentWindowMetrics.windowInsets)
-    }
-
-    fun maximumWindowBounds(@UiContext context: Context): Rect {
-        val wm = context.getSystemService(WindowManager::class.java)
-        return wm.maximumWindowMetrics.bounds
-    }
-
-    /**
-     * Computes the [WindowInsetsCompat] for platforms above [Build.VERSION_CODES.R], inclusive.
-     * @DoNotInline required for implementation-specific class method to prevent it from being
-     * inlined.
-     *
-     * @see androidx.window.layout.WindowMetrics.getWindowInsets
-     */
-    @DoNotInline
-    fun currentWindowInsets(activity: Activity): WindowInsetsCompat {
-        val platformInsets = activity.windowManager.currentWindowMetrics.windowInsets
-        return WindowInsetsCompat.toWindowInsetsCompat(platformInsets)
-    }
-}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculatorCompat.kt b/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculatorCompat.kt
index 72e8ffa..5087722 100644
--- a/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculatorCompat.kt
+++ b/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculatorCompat.kt
@@ -34,11 +34,11 @@
 import androidx.core.view.WindowInsetsCompat
 import androidx.window.core.Bounds
 import androidx.window.layout.util.ActivityCompatHelperApi24.isInMultiWindowMode
+import androidx.window.layout.util.ContextCompatHelper.unwrapUiContext
 import androidx.window.layout.util.ContextCompatHelperApi30.currentWindowBounds
 import androidx.window.layout.util.ContextCompatHelperApi30.currentWindowInsets
 import androidx.window.layout.util.ContextCompatHelperApi30.currentWindowMetrics
 import androidx.window.layout.util.ContextCompatHelperApi30.maximumWindowBounds
-import androidx.window.layout.util.ContextUtils.unwrapUiContext
 import androidx.window.layout.util.DisplayCompatHelperApi17.getRealSize
 import androidx.window.layout.util.DisplayCompatHelperApi28.safeInsetBottom
 import androidx.window.layout.util.DisplayCompatHelperApi28.safeInsetLeft
diff --git a/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt b/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
index eaafa91..b5b1422 100644
--- a/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
+++ b/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
@@ -16,17 +16,65 @@
 
 package androidx.window.layout.util
 
+import android.app.Activity
 import android.content.Context
+import android.content.ContextWrapper
 import android.graphics.Rect
+import android.inputmethodservice.InputMethodService
 import android.os.Build
 import android.view.WindowManager
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.UiContext
 import androidx.core.view.WindowInsetsCompat
+import androidx.window.layout.WindowMetrics
+
+internal object ContextCompatHelper {
+    /**
+     * Given a [UiContext], check if it is a [ContextWrapper]. If so, we need to unwrap it and
+     * return the actual [UiContext] within.
+     */
+    @UiContext
+    internal fun unwrapUiContext(@UiContext context: Context): Context {
+        var iterator = context
+
+        while (iterator is ContextWrapper) {
+            if (iterator is Activity) {
+                // Activities are always ContextWrappers
+                return iterator
+            } else if (iterator is InputMethodService) {
+                // InputMethodService are always ContextWrappers
+                return iterator
+            } else if (iterator.baseContext == null) {
+                return iterator
+            }
+
+            iterator = iterator.baseContext
+        }
+
+        // TODO(b/259148796): This code path is not needed for APIs R and above. However, that is
+        //  not clear and also not enforced anywhere. Once we move to version-based implementations,
+        //  this ambiguity will no longer exist. Again for clarity, on APIs before R, UiContexts are
+        //  Activities or InputMethodServices, so we should never reach this point.
+        throw IllegalArgumentException("Context $context is not a UiContext")
+    }
+}
+
+@RequiresApi(Build.VERSION_CODES.N)
+internal object ContextCompatHelperApi24 {
+    fun isInMultiWindowMode(activity: Activity): Boolean {
+        return activity.isInMultiWindowMode
+    }
+}
 
 @RequiresApi(Build.VERSION_CODES.R)
-internal object ContextCompatHelper {
+internal object ContextCompatHelperApi30 {
+
+    fun currentWindowMetrics(@UiContext context: Context): WindowMetrics {
+        val wm = context.getSystemService(WindowManager::class.java)
+        val insets = WindowInsetsCompat.toWindowInsetsCompat(wm.currentWindowMetrics.windowInsets)
+        return WindowMetrics(wm.currentWindowMetrics.bounds, insets)
+    }
 
     fun currentWindowBounds(@UiContext context: Context): Rect {
         val wm = context.getSystemService(WindowManager::class.java)
@@ -47,8 +95,8 @@
      */
     @DoNotInline
     fun currentWindowInsets(@UiContext context: Context): WindowInsetsCompat {
-        val wm = context.getSystemService(WindowManager::class.java)
-        val platformInsets = wm.currentWindowMetrics.windowInsets
+        val platformInsets = context.getSystemService(WindowManager::class.java)
+            .currentWindowMetrics.windowInsets
         return WindowInsetsCompat.toWindowInsetsCompat(platformInsets)
     }
 }
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/layout/util/ContextUtils.kt b/window/window/src/main/java/androidx/window/layout/util/ContextUtils.kt
deleted file mode 100644
index 070bdf4..0000000
--- a/window/window/src/main/java/androidx/window/layout/util/ContextUtils.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.window.layout.util
-
-import android.app.Activity
-import android.content.Context
-import android.content.ContextWrapper
-import android.inputmethodservice.InputMethodService
-import androidx.annotation.UiContext
-
-internal object ContextUtils {
-    /**
-     * Given a [UiContext], check if it is a [ContextWrapper]. If so, we need to unwrap it and
-     * return the actual [UiContext] within.
-     */
-    @UiContext
-    internal fun unwrapUiContext(@UiContext context: Context): Context {
-        var iterator = context
-
-        while (iterator is ContextWrapper) {
-            if (iterator is Activity) {
-                // Activities are always ContextWrappers
-                return iterator
-            } else if (iterator is InputMethodService) {
-                // InputMethodService are always ContextWrappers
-                return iterator
-            } else if (iterator.baseContext == null) {
-                return iterator
-            }
-
-            iterator = iterator.baseContext
-        }
-
-        // TODO(b/259148796): This code path is not needed for APIs R and above. However, that is
-        //  not clear and also not enforced anywhere. Once we move to version-based implementations,
-        //  this ambiguity will no longer exist. Again for clarity, on APIs before R, UiContexts are
-        //  Activities or InputMethodServices, so we should never reach this point.
-        throw IllegalArgumentException("Context $context is not a UiContext")
-    }
-}
diff --git a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt
index 72b7c6d..eb9c205 100644
--- a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt
@@ -18,12 +18,15 @@
 
 import android.app.Activity
 import android.content.Context
+import android.os.Binder
+import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
 /**
@@ -56,4 +59,34 @@
 
         assertFalse(activityEmbeddingController.isActivityEmbedded(mockActivity))
     }
+
+    @Test
+    @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+    fun testGetActivityStack() {
+        val activityStack = ActivityStack(listOf(), true, Binder())
+        whenever(mockEmbeddingBackend.getActivityStack(mockActivity)).thenReturn(activityStack)
+
+        assertEquals(activityStack, activityEmbeddingController.getActivityStack(mockActivity))
+    }
+
+    @Test
+    @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+    fun testIsFinishingActivityStacksSupported() {
+        whenever(mockEmbeddingBackend.isFinishActivityStacksSupported()).thenReturn(true)
+
+        assertTrue(activityEmbeddingController.isFinishingActivityStacksSupported())
+
+        whenever(mockEmbeddingBackend.isFinishActivityStacksSupported()).thenReturn(false)
+
+        assertFalse(activityEmbeddingController.isFinishingActivityStacksSupported())
+    }
+
+    @Test
+    @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+    fun testFinishActivityStacks() {
+        val activityStacks: Set<ActivityStack> = mock()
+        activityEmbeddingController.finishActivityStacks(activityStacks)
+
+        verify(mockEmbeddingBackend).finishActivityStacks(activityStacks)
+    }
 }
\ No newline at end of file
diff --git a/window/window/src/test/java/androidx/window/embedding/ActivityRuleTest.kt b/window/window/src/test/java/androidx/window/embedding/ActivityRuleTest.kt
index 8af21e3..d5cce1a 100644
--- a/window/window/src/test/java/androidx/window/embedding/ActivityRuleTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/ActivityRuleTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.window.embedding
 
-import android.content.ComponentName
 import androidx.window.core.ActivityComponentInfo
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -80,12 +79,8 @@
     @Test
     fun test_ActivityRule_Builder() {
         val filters = HashSet<ActivityFilter>()
-        filters.add(
-            ActivityFilter(
-                ComponentName("a", "b"),
-                "ACTION"
-            )
-        )
+        filters.add(FILTER_WITH_ACTIVITY)
+
         val rule = ActivityRule.Builder(filters)
             .setAlwaysExpand(true)
             .setTag(TEST_TAG)
@@ -95,6 +90,23 @@
         assertEquals(filters, rule.filters)
     }
 
+    @Test
+    fun testToString() {
+        val filters = HashSet<ActivityFilter>()
+        filters.add(FILTER_WITH_ACTIVITY)
+        val alwaysExpand = true
+
+        val ruleString = ActivityRule.Builder(filters)
+            .setAlwaysExpand(alwaysExpand)
+            .setTag(TEST_TAG)
+            .build()
+            .toString()
+
+        assertTrue(ruleString.contains(filters.toString()))
+        assertTrue(ruleString.contains(alwaysExpand.toString()))
+        assertTrue(ruleString.contains(TEST_TAG))
+    }
+
     companion object {
         private const val TEST_TAG = "test"
         val FILTER_WITH_ACTIVITY = ActivityFilter(
diff --git a/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt b/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt
index 780bcf9..b207678 100644
--- a/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt
@@ -25,6 +25,7 @@
 import org.junit.Test
 import org.mockito.kotlin.mock
 
+/** Unit tests for [SplitInfo] */
 class SplitInfoTest {
 
     @Test
@@ -65,6 +66,35 @@
         assertEquals(firstInfo.hashCode(), secondInfo.hashCode())
     }
 
+    @Test
+    fun testSplitInfoProperties() {
+        val activity = mock<Activity>()
+        val firstStack = createTestActivityStack(emptyList())
+        val secondStack = createTestActivityStack(listOf(activity))
+        val attributes = SplitAttributes()
+        val token = Binder()
+        val splitInfo = SplitInfo(firstStack, secondStack, attributes, token)
+
+        assertEquals(firstStack, splitInfo.primaryActivityStack)
+        assertEquals(secondStack, splitInfo.secondaryActivityStack)
+        assertEquals(attributes, splitInfo.splitAttributes)
+    }
+
+    @Test
+    fun testToString() {
+        val activity = mock<Activity>()
+        val firstStack = createTestActivityStack(emptyList())
+        val secondStack = createTestActivityStack(listOf(activity))
+        val attributes = SplitAttributes()
+        val token = Binder()
+        val splitInfoString = SplitInfo(firstStack, secondStack, attributes, token).toString()
+
+        assertTrue(splitInfoString.contains(firstStack.toString()))
+        assertTrue(splitInfoString.contains(secondStack.toString()))
+        assertTrue(splitInfoString.contains(attributes.toString()))
+        assertTrue(splitInfoString.contains(token.toString()))
+    }
+
     private fun createTestActivityStack(
         activitiesInProcess: List<Activity>,
         isEmpty: Boolean = false,
diff --git a/window/window/src/test/java/androidx/window/embedding/SplitPairRuleTest.kt b/window/window/src/test/java/androidx/window/embedding/SplitPairRuleTest.kt
index d02808d..eeaa0f8 100644
--- a/window/window/src/test/java/androidx/window/embedding/SplitPairRuleTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/SplitPairRuleTest.kt
@@ -303,6 +303,55 @@
         assertTrue(rule.checkParentBounds(density, bounds))
     }
 
+    @Test
+    fun testToString() {
+        val filters = HashSet<SplitPairFilter>()
+        val minWidthDp = 123
+        val minHeightDp = 456
+        val minSmallestMinWidthDp = 789
+        val maxAspectRatioInPortrait = EmbeddingAspectRatio.ratio(1.23f)
+        val maxAspectRatioInLandscape = EmbeddingAspectRatio.ratio(4.56f)
+        val finishPrimaryWithSecondary = SplitRule.FinishBehavior.ADJACENT
+        val finishSecondaryWithPrimary = SplitRule.FinishBehavior.ADJACENT
+        val clearTop = true
+        val expectedSplitAttributes = SplitAttributes.Builder()
+            .setSplitType(SplitAttributes.SplitType.ratio(0.3f))
+            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+            .build()
+        filters.add(
+            SplitPairFilter(
+                ActivityComponentInfo("a", "b"),
+                ActivityComponentInfo("c", "d"),
+                "ACTION"
+            )
+        )
+        val ruleString = SplitPairRule.Builder(filters)
+            .setMinWidthDp(minWidthDp)
+            .setMinHeightDp(minHeightDp)
+            .setMinSmallestWidthDp(minSmallestMinWidthDp)
+            .setMaxAspectRatioInPortrait(maxAspectRatioInPortrait)
+            .setMaxAspectRatioInLandscape(maxAspectRatioInLandscape)
+            .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
+            .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
+            .setClearTop(clearTop)
+            .setDefaultSplitAttributes(expectedSplitAttributes)
+            .setTag(TEST_TAG)
+            .build()
+            .toString()
+
+        assertTrue(ruleString.contains(filters.toString()))
+        assertTrue(ruleString.contains(minHeightDp.toString()))
+        assertTrue(ruleString.contains(minWidthDp.toString()))
+        assertTrue(ruleString.contains(minSmallestMinWidthDp.toString()))
+        assertTrue(ruleString.contains(maxAspectRatioInPortrait.toString()))
+        assertTrue(ruleString.contains(maxAspectRatioInLandscape.toString()))
+        assertTrue(ruleString.contains(finishPrimaryWithSecondary.toString()))
+        assertTrue(ruleString.contains(finishSecondaryWithPrimary.toString()))
+        assertTrue(ruleString.contains(clearTop.toString()))
+        assertTrue(ruleString.contains(expectedSplitAttributes.toString()))
+        assertTrue(ruleString.contains(TEST_TAG))
+    }
+
     companion object {
 
         private const val density = 2f
diff --git a/window/window/src/test/java/androidx/window/embedding/SplitPlaceHolderRuleTest.kt b/window/window/src/test/java/androidx/window/embedding/SplitPlaceHolderRuleTest.kt
index 0fa21d3..d8cc0fa 100644
--- a/window/window/src/test/java/androidx/window/embedding/SplitPlaceHolderRuleTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/SplitPlaceHolderRuleTest.kt
@@ -288,6 +288,50 @@
         assertTrue(rule.checkParentBounds(density, bounds))
     }
 
+    @Test
+    fun testToString() {
+        val filters = HashSet<ActivityFilter>()
+        val intent = Intent()
+        val minWidthDp = 123
+        val minHeightDp = 456
+        val minSmallestMinWidthDp = 789
+        val maxAspectRatioInPortrait = EmbeddingAspectRatio.ratio(1.23f)
+        val maxAspectRatioInLandscape = EmbeddingAspectRatio.ratio(4.56f)
+        val finishPrimaryWithPlaceholder = SplitRule.FinishBehavior.ADJACENT
+        val expectedSplitAttributes = SplitAttributes.Builder()
+            .setSplitType(SplitAttributes.SplitType.ratio(0.3f))
+            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+            .build()
+        filters.add(
+            ActivityFilter(
+                ActivityComponentInfo("a", "b"),
+                "ACTION"
+            )
+        )
+        val ruleString = SplitPlaceholderRule.Builder(filters, intent)
+            .setMinWidthDp(minWidthDp)
+            .setMinHeightDp(minHeightDp)
+            .setMinSmallestWidthDp(minSmallestMinWidthDp)
+            .setMaxAspectRatioInPortrait(maxAspectRatioInPortrait)
+            .setMaxAspectRatioInLandscape(maxAspectRatioInLandscape)
+            .setFinishPrimaryWithPlaceholder(finishPrimaryWithPlaceholder)
+            .setDefaultSplitAttributes(expectedSplitAttributes)
+            .setTag(TEST_TAG)
+            .build()
+            .toString()
+
+        assertTrue(ruleString.contains(filters.toString()))
+        assertTrue(ruleString.contains(intent.toString()))
+        assertTrue(ruleString.contains(minHeightDp.toString()))
+        assertTrue(ruleString.contains(minWidthDp.toString()))
+        assertTrue(ruleString.contains(minSmallestMinWidthDp.toString()))
+        assertTrue(ruleString.contains(maxAspectRatioInPortrait.toString()))
+        assertTrue(ruleString.contains(maxAspectRatioInLandscape.toString()))
+        assertTrue(ruleString.contains(finishPrimaryWithPlaceholder.toString()))
+        assertTrue(ruleString.contains(expectedSplitAttributes.toString()))
+        assertTrue(ruleString.contains(TEST_TAG))
+    }
+
     companion object {
 
         private const val density = 2f