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 <: T2 and T2 <: T3 are
+ * known, then T1 <: T3 will be inferred automatically, where <: 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 <: 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 <: 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 < OPTIONAL < 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 'onValueChange' with type Function1<? 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 'postPointerSlop' with type Function2<? 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 'onValueChange' with type Function1<? 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<Function2<Boolean, Float, Unit>> of 'getOnDrag'."
- errorLine1=" val onDrag: State<(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 'onDelta' with type Function1<? 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 'initialOnValueChange' with type Function1<? 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 '<set-?>' with type Function1<? 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<FloatRange, Unit> of 'getOnValueChange$lint_module'."
+ 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<Boolean, Float, Unit> of 'getOnDrag$lint_module'."
+ 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 'positionalThreshold' with type Function2<? 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