Project import generated by Copybara.
FolderOrigin-RevId: /usr/local/google/home/diegovela/androidx-platform-dev/frameworks/support/.
Relnote: "Add APIs for SplitRule, SplitAttributes,
SplitAttributesCalculator."
Bug: 266471547
Test: n/a making some changes public.
Merged-In: I92d234efb38620fcefc85adb4cc9ef79e0a515fc
Change-Id: I92d234efb38620fcefc85adb4cc9ef79e0a515fc
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 498ff9f..3515834 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -361,6 +361,7 @@
docs(project(":window:window-rxjava2"))
docs(project(":window:window-rxjava3"))
stubs(project(":window:sidecar:sidecar"))
+ samples(project(":window:window-samples:"))
stubs(project(":window:extensions:extensions"))
stubs(project(":window:extensions:core:core"))
docs(project(":window:window-testing"))
diff --git a/settings.gradle b/settings.gradle
index 4c67df4..cc4848c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -983,17 +983,20 @@
includeProject(":wear:watchface:watchface-style-old-api-test-stub", "wear/watchface/watchface-style/old-api-test-stub", [BuildType.MAIN, BuildType.WEAR])
includeProject(":webkit:integration-tests:testapp", [BuildType.MAIN])
includeProject(":webkit:webkit", [BuildType.MAIN])
-includeProject(":window:window", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA, BuildType.WINDOW])
-includeProject(":window:extensions:extensions", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA, BuildType.WINDOW])
-includeProject(":window:extensions:core:core", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA, BuildType.WINDOW])
-includeProject(":window:integration-tests:configuration-change-tests", [BuildType.MAIN, BuildType.WINDOW])
-includeProject(":window:sidecar:sidecar", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA, BuildType.WINDOW])
-includeProject(":window:window-java", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA, BuildType.WINDOW])
-includeProject(":window:window-core", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA, BuildType.WINDOW])
-includeProject(":window:window-rxjava2", [BuildType.MAIN, BuildType.WINDOW])
-includeProject(":window:window-rxjava3", [BuildType.MAIN, BuildType.WINDOW])
-includeProject(":window:window-samples", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA, BuildType.WINDOW])
-includeProject(":window:window-testing", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA, BuildType.WINDOW])
+includeProject(":window:window", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:window-samples", "window/window/samples", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:extensions:extensions", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:extensions:core:core", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:integration-tests:configuration-change-tests", [BuildType.MAIN])
+includeProject(":window:sidecar:sidecar", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:window-java", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:window-core", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:window-rxjava2", [BuildType.MAIN])
+includeProject(":window:window-rxjava3", [BuildType.MAIN])
+includeProject(":window:window-demos:demo", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:window-demos:demo-common", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:window-demos:demo-second-app", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
+includeProject(":window:window-testing", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
includeProject(":work:integration-tests:testapp", [BuildType.MAIN])
includeProject(":work:work-benchmark", [BuildType.MAIN])
includeProject(":work:work-gcm", [BuildType.MAIN])
diff --git a/window/extensions/extensions/api/current.txt b/window/extensions/extensions/api/current.txt
index b27ec92..f0c699b 100644
--- a/window/extensions/extensions/api/current.txt
+++ b/window/extensions/extensions/api/current.txt
@@ -16,10 +16,13 @@
package androidx.window.extensions.embedding {
public interface ActivityEmbeddingComponent {
+ method public void clearSplitAttributesCalculator();
method public void clearSplitInfoCallback();
method public boolean isActivityEmbedded(android.app.Activity);
method public void setEmbeddingRules(java.util.Set<androidx.window.extensions.embedding.EmbeddingRule!>);
- method public void setSplitInfoCallback(java.util.function.Consumer<java.util.List<androidx.window.extensions.embedding.SplitInfo!>!>);
+ method public void setSplitAttributesCalculator(androidx.window.extensions.core.util.function.Function<androidx.window.extensions.embedding.SplitAttributesCalculatorParams!,androidx.window.extensions.embedding.SplitAttributes!>);
+ method @Deprecated public void setSplitInfoCallback(java.util.function.Consumer<java.util.List<androidx.window.extensions.embedding.SplitInfo!>!>);
+ method public default void setSplitInfoCallback(androidx.window.extensions.core.util.function.Consumer<java.util.List<androidx.window.extensions.embedding.SplitInfo!>!>);
}
public class ActivityRule extends androidx.window.extensions.embedding.EmbeddingRule {
@@ -29,9 +32,11 @@
}
public static final class ActivityRule.Builder {
- ctor public ActivityRule.Builder(java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>);
+ ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.N) public ActivityRule.Builder(java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>);
+ ctor public ActivityRule.Builder(androidx.window.extensions.core.util.function.Predicate<android.app.Activity!>, androidx.window.extensions.core.util.function.Predicate<android.content.Intent!>);
method public androidx.window.extensions.embedding.ActivityRule build();
method public androidx.window.extensions.embedding.ActivityRule.Builder setShouldAlwaysExpand(boolean);
+ method public androidx.window.extensions.embedding.ActivityRule.Builder setTag(String);
}
public class ActivityStack {
@@ -40,12 +45,63 @@
}
public abstract class EmbeddingRule {
+ method public String? getTag();
+ }
+
+ public class SplitAttributes {
+ method @ColorInt public int getAnimationBackgroundColor();
+ method public int getLayoutDirection();
+ method public androidx.window.extensions.embedding.SplitAttributes.SplitType getSplitType();
+ }
+
+ public static final class SplitAttributes.Builder {
+ ctor public SplitAttributes.Builder();
+ method public androidx.window.extensions.embedding.SplitAttributes build();
+ method public androidx.window.extensions.embedding.SplitAttributes.Builder setAnimationBackgroundColor(@ColorInt int);
+ method public androidx.window.extensions.embedding.SplitAttributes.Builder setLayoutDirection(int);
+ method public androidx.window.extensions.embedding.SplitAttributes.Builder setSplitType(androidx.window.extensions.embedding.SplitAttributes.SplitType);
+ }
+
+ public static final class SplitAttributes.LayoutDirection {
+ field public static final int BOTTOM_TO_TOP = 5; // 0x5
+ field public static final int LEFT_TO_RIGHT = 0; // 0x0
+ field public static final int LOCALE = 3; // 0x3
+ field public static final int RIGHT_TO_LEFT = 1; // 0x1
+ field public static final int TOP_TO_BOTTOM = 4; // 0x4
+ }
+
+ public static class SplitAttributes.SplitType {
+ }
+
+ public static final class SplitAttributes.SplitType.ExpandContainersSplitType extends androidx.window.extensions.embedding.SplitAttributes.SplitType {
+ ctor public SplitAttributes.SplitType.ExpandContainersSplitType();
+ }
+
+ public static final class SplitAttributes.SplitType.HingeSplitType extends androidx.window.extensions.embedding.SplitAttributes.SplitType {
+ ctor public SplitAttributes.SplitType.HingeSplitType(androidx.window.extensions.embedding.SplitAttributes.SplitType);
+ method public androidx.window.extensions.embedding.SplitAttributes.SplitType getFallbackSplitType();
+ }
+
+ public static final class SplitAttributes.SplitType.RatioSplitType extends androidx.window.extensions.embedding.SplitAttributes.SplitType {
+ ctor public SplitAttributes.SplitType.RatioSplitType(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float);
+ method @FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) public float getRatio();
+ method public static androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType splitEqually();
+ }
+
+ public class SplitAttributesCalculatorParams {
+ method public boolean areDefaultConstraintsSatisfied();
+ method public androidx.window.extensions.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public android.content.res.Configuration getParentConfiguration();
+ method public androidx.window.extensions.layout.WindowLayoutInfo getParentWindowLayoutInfo();
+ method public android.view.WindowMetrics getParentWindowMetrics();
+ method public String? getSplitRuleTag();
}
public class SplitInfo {
method public androidx.window.extensions.embedding.ActivityStack getPrimaryActivityStack();
method public androidx.window.extensions.embedding.ActivityStack getSecondaryActivityStack();
- method public float getSplitRatio();
+ method public androidx.window.extensions.embedding.SplitAttributes getSplitAttributes();
+ method @Deprecated public float getSplitRatio();
}
public class SplitPairRule extends androidx.window.extensions.embedding.SplitRule {
@@ -57,15 +113,18 @@
}
public static final class SplitPairRule.Builder {
- ctor public SplitPairRule.Builder(java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.app.Activity!>!>, java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.content.Intent!>!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.N) public SplitPairRule.Builder(java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.app.Activity!>!>, java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.content.Intent!>!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor public SplitPairRule.Builder(androidx.window.extensions.core.util.function.Predicate<android.util.Pair<android.app.Activity!,android.app.Activity!>!>, androidx.window.extensions.core.util.function.Predicate<android.util.Pair<android.app.Activity!,android.content.Intent!>!>, androidx.window.extensions.core.util.function.Predicate<android.view.WindowMetrics!>);
method public androidx.window.extensions.embedding.SplitPairRule build();
+ method public androidx.window.extensions.embedding.SplitPairRule.Builder setDefaultSplitAttributes(androidx.window.extensions.embedding.SplitAttributes);
method public androidx.window.extensions.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int);
method public androidx.window.extensions.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int);
- method public androidx.window.extensions.embedding.SplitPairRule.Builder setLayoutDirection(int);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setLayoutDirection(int);
method public androidx.window.extensions.embedding.SplitPairRule.Builder setShouldClearTop(boolean);
method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setShouldFinishPrimaryWithSecondary(boolean);
method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setShouldFinishSecondaryWithPrimary(boolean);
- method public androidx.window.extensions.embedding.SplitPairRule.Builder setSplitRatio(float);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float);
+ method public androidx.window.extensions.embedding.SplitPairRule.Builder setTag(String);
}
public class SplitPlaceholderRule extends androidx.window.extensions.embedding.SplitRule {
@@ -78,19 +137,23 @@
}
public static final class SplitPlaceholderRule.Builder {
- ctor public SplitPlaceholderRule.Builder(android.content.Intent, java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.N) public SplitPlaceholderRule.Builder(android.content.Intent, java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor public SplitPlaceholderRule.Builder(android.content.Intent, androidx.window.extensions.core.util.function.Predicate<android.app.Activity!>, androidx.window.extensions.core.util.function.Predicate<android.content.Intent!>, androidx.window.extensions.core.util.function.Predicate<android.view.WindowMetrics!>);
method public androidx.window.extensions.embedding.SplitPlaceholderRule build();
+ method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setDefaultSplitAttributes(androidx.window.extensions.embedding.SplitAttributes);
method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int);
method @Deprecated public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithSecondary(int);
- method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int);
- method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setSplitRatio(float);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float);
method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setSticky(boolean);
+ method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setTag(String);
}
public abstract class SplitRule extends androidx.window.extensions.embedding.EmbeddingRule {
method @RequiresApi(api=android.os.Build.VERSION_CODES.N) public boolean checkParentMetrics(android.view.WindowMetrics);
- method public int getLayoutDirection();
- method public float getSplitRatio();
+ method public androidx.window.extensions.embedding.SplitAttributes getDefaultSplitAttributes();
+ method @Deprecated public int getLayoutDirection();
+ method @Deprecated public float getSplitRatio();
field public static final int FINISH_ADJACENT = 2; // 0x2
field public static final int FINISH_ALWAYS = 1; // 0x1
field public static final int FINISH_NEVER = 0; // 0x0
@@ -116,8 +179,11 @@
}
public interface WindowLayoutComponent {
- method public void addWindowLayoutInfoListener(android.app.Activity, java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
- method public void removeWindowLayoutInfoListener(java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method @Deprecated public void addWindowLayoutInfoListener(android.app.Activity, java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method @Deprecated public default void addWindowLayoutInfoListener(@UiContext android.content.Context, java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method public default void addWindowLayoutInfoListener(@UiContext android.content.Context, androidx.window.extensions.core.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method @Deprecated public void removeWindowLayoutInfoListener(java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method public default void removeWindowLayoutInfoListener(androidx.window.extensions.core.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
}
public class WindowLayoutInfo {
diff --git a/window/extensions/extensions/api/public_plus_experimental_current.txt b/window/extensions/extensions/api/public_plus_experimental_current.txt
index b27ec92..f0c699b 100644
--- a/window/extensions/extensions/api/public_plus_experimental_current.txt
+++ b/window/extensions/extensions/api/public_plus_experimental_current.txt
@@ -16,10 +16,13 @@
package androidx.window.extensions.embedding {
public interface ActivityEmbeddingComponent {
+ method public void clearSplitAttributesCalculator();
method public void clearSplitInfoCallback();
method public boolean isActivityEmbedded(android.app.Activity);
method public void setEmbeddingRules(java.util.Set<androidx.window.extensions.embedding.EmbeddingRule!>);
- method public void setSplitInfoCallback(java.util.function.Consumer<java.util.List<androidx.window.extensions.embedding.SplitInfo!>!>);
+ method public void setSplitAttributesCalculator(androidx.window.extensions.core.util.function.Function<androidx.window.extensions.embedding.SplitAttributesCalculatorParams!,androidx.window.extensions.embedding.SplitAttributes!>);
+ method @Deprecated public void setSplitInfoCallback(java.util.function.Consumer<java.util.List<androidx.window.extensions.embedding.SplitInfo!>!>);
+ method public default void setSplitInfoCallback(androidx.window.extensions.core.util.function.Consumer<java.util.List<androidx.window.extensions.embedding.SplitInfo!>!>);
}
public class ActivityRule extends androidx.window.extensions.embedding.EmbeddingRule {
@@ -29,9 +32,11 @@
}
public static final class ActivityRule.Builder {
- ctor public ActivityRule.Builder(java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>);
+ ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.N) public ActivityRule.Builder(java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>);
+ ctor public ActivityRule.Builder(androidx.window.extensions.core.util.function.Predicate<android.app.Activity!>, androidx.window.extensions.core.util.function.Predicate<android.content.Intent!>);
method public androidx.window.extensions.embedding.ActivityRule build();
method public androidx.window.extensions.embedding.ActivityRule.Builder setShouldAlwaysExpand(boolean);
+ method public androidx.window.extensions.embedding.ActivityRule.Builder setTag(String);
}
public class ActivityStack {
@@ -40,12 +45,63 @@
}
public abstract class EmbeddingRule {
+ method public String? getTag();
+ }
+
+ public class SplitAttributes {
+ method @ColorInt public int getAnimationBackgroundColor();
+ method public int getLayoutDirection();
+ method public androidx.window.extensions.embedding.SplitAttributes.SplitType getSplitType();
+ }
+
+ public static final class SplitAttributes.Builder {
+ ctor public SplitAttributes.Builder();
+ method public androidx.window.extensions.embedding.SplitAttributes build();
+ method public androidx.window.extensions.embedding.SplitAttributes.Builder setAnimationBackgroundColor(@ColorInt int);
+ method public androidx.window.extensions.embedding.SplitAttributes.Builder setLayoutDirection(int);
+ method public androidx.window.extensions.embedding.SplitAttributes.Builder setSplitType(androidx.window.extensions.embedding.SplitAttributes.SplitType);
+ }
+
+ public static final class SplitAttributes.LayoutDirection {
+ field public static final int BOTTOM_TO_TOP = 5; // 0x5
+ field public static final int LEFT_TO_RIGHT = 0; // 0x0
+ field public static final int LOCALE = 3; // 0x3
+ field public static final int RIGHT_TO_LEFT = 1; // 0x1
+ field public static final int TOP_TO_BOTTOM = 4; // 0x4
+ }
+
+ public static class SplitAttributes.SplitType {
+ }
+
+ public static final class SplitAttributes.SplitType.ExpandContainersSplitType extends androidx.window.extensions.embedding.SplitAttributes.SplitType {
+ ctor public SplitAttributes.SplitType.ExpandContainersSplitType();
+ }
+
+ public static final class SplitAttributes.SplitType.HingeSplitType extends androidx.window.extensions.embedding.SplitAttributes.SplitType {
+ ctor public SplitAttributes.SplitType.HingeSplitType(androidx.window.extensions.embedding.SplitAttributes.SplitType);
+ method public androidx.window.extensions.embedding.SplitAttributes.SplitType getFallbackSplitType();
+ }
+
+ public static final class SplitAttributes.SplitType.RatioSplitType extends androidx.window.extensions.embedding.SplitAttributes.SplitType {
+ ctor public SplitAttributes.SplitType.RatioSplitType(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float);
+ method @FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) public float getRatio();
+ method public static androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType splitEqually();
+ }
+
+ public class SplitAttributesCalculatorParams {
+ method public boolean areDefaultConstraintsSatisfied();
+ method public androidx.window.extensions.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public android.content.res.Configuration getParentConfiguration();
+ method public androidx.window.extensions.layout.WindowLayoutInfo getParentWindowLayoutInfo();
+ method public android.view.WindowMetrics getParentWindowMetrics();
+ method public String? getSplitRuleTag();
}
public class SplitInfo {
method public androidx.window.extensions.embedding.ActivityStack getPrimaryActivityStack();
method public androidx.window.extensions.embedding.ActivityStack getSecondaryActivityStack();
- method public float getSplitRatio();
+ method public androidx.window.extensions.embedding.SplitAttributes getSplitAttributes();
+ method @Deprecated public float getSplitRatio();
}
public class SplitPairRule extends androidx.window.extensions.embedding.SplitRule {
@@ -57,15 +113,18 @@
}
public static final class SplitPairRule.Builder {
- ctor public SplitPairRule.Builder(java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.app.Activity!>!>, java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.content.Intent!>!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.N) public SplitPairRule.Builder(java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.app.Activity!>!>, java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.content.Intent!>!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor public SplitPairRule.Builder(androidx.window.extensions.core.util.function.Predicate<android.util.Pair<android.app.Activity!,android.app.Activity!>!>, androidx.window.extensions.core.util.function.Predicate<android.util.Pair<android.app.Activity!,android.content.Intent!>!>, androidx.window.extensions.core.util.function.Predicate<android.view.WindowMetrics!>);
method public androidx.window.extensions.embedding.SplitPairRule build();
+ method public androidx.window.extensions.embedding.SplitPairRule.Builder setDefaultSplitAttributes(androidx.window.extensions.embedding.SplitAttributes);
method public androidx.window.extensions.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int);
method public androidx.window.extensions.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int);
- method public androidx.window.extensions.embedding.SplitPairRule.Builder setLayoutDirection(int);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setLayoutDirection(int);
method public androidx.window.extensions.embedding.SplitPairRule.Builder setShouldClearTop(boolean);
method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setShouldFinishPrimaryWithSecondary(boolean);
method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setShouldFinishSecondaryWithPrimary(boolean);
- method public androidx.window.extensions.embedding.SplitPairRule.Builder setSplitRatio(float);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float);
+ method public androidx.window.extensions.embedding.SplitPairRule.Builder setTag(String);
}
public class SplitPlaceholderRule extends androidx.window.extensions.embedding.SplitRule {
@@ -78,19 +137,23 @@
}
public static final class SplitPlaceholderRule.Builder {
- ctor public SplitPlaceholderRule.Builder(android.content.Intent, java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.N) public SplitPlaceholderRule.Builder(android.content.Intent, java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor public SplitPlaceholderRule.Builder(android.content.Intent, androidx.window.extensions.core.util.function.Predicate<android.app.Activity!>, androidx.window.extensions.core.util.function.Predicate<android.content.Intent!>, androidx.window.extensions.core.util.function.Predicate<android.view.WindowMetrics!>);
method public androidx.window.extensions.embedding.SplitPlaceholderRule build();
+ method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setDefaultSplitAttributes(androidx.window.extensions.embedding.SplitAttributes);
method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int);
method @Deprecated public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithSecondary(int);
- method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int);
- method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setSplitRatio(float);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float);
method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setSticky(boolean);
+ method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setTag(String);
}
public abstract class SplitRule extends androidx.window.extensions.embedding.EmbeddingRule {
method @RequiresApi(api=android.os.Build.VERSION_CODES.N) public boolean checkParentMetrics(android.view.WindowMetrics);
- method public int getLayoutDirection();
- method public float getSplitRatio();
+ method public androidx.window.extensions.embedding.SplitAttributes getDefaultSplitAttributes();
+ method @Deprecated public int getLayoutDirection();
+ method @Deprecated public float getSplitRatio();
field public static final int FINISH_ADJACENT = 2; // 0x2
field public static final int FINISH_ALWAYS = 1; // 0x1
field public static final int FINISH_NEVER = 0; // 0x0
@@ -116,8 +179,11 @@
}
public interface WindowLayoutComponent {
- method public void addWindowLayoutInfoListener(android.app.Activity, java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
- method public void removeWindowLayoutInfoListener(java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method @Deprecated public void addWindowLayoutInfoListener(android.app.Activity, java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method @Deprecated public default void addWindowLayoutInfoListener(@UiContext android.content.Context, java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method public default void addWindowLayoutInfoListener(@UiContext android.content.Context, androidx.window.extensions.core.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method @Deprecated public void removeWindowLayoutInfoListener(java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method public default void removeWindowLayoutInfoListener(androidx.window.extensions.core.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
}
public class WindowLayoutInfo {
diff --git a/window/extensions/extensions/api/restricted_current.txt b/window/extensions/extensions/api/restricted_current.txt
index b27ec92..f0c699b 100644
--- a/window/extensions/extensions/api/restricted_current.txt
+++ b/window/extensions/extensions/api/restricted_current.txt
@@ -16,10 +16,13 @@
package androidx.window.extensions.embedding {
public interface ActivityEmbeddingComponent {
+ method public void clearSplitAttributesCalculator();
method public void clearSplitInfoCallback();
method public boolean isActivityEmbedded(android.app.Activity);
method public void setEmbeddingRules(java.util.Set<androidx.window.extensions.embedding.EmbeddingRule!>);
- method public void setSplitInfoCallback(java.util.function.Consumer<java.util.List<androidx.window.extensions.embedding.SplitInfo!>!>);
+ method public void setSplitAttributesCalculator(androidx.window.extensions.core.util.function.Function<androidx.window.extensions.embedding.SplitAttributesCalculatorParams!,androidx.window.extensions.embedding.SplitAttributes!>);
+ method @Deprecated public void setSplitInfoCallback(java.util.function.Consumer<java.util.List<androidx.window.extensions.embedding.SplitInfo!>!>);
+ method public default void setSplitInfoCallback(androidx.window.extensions.core.util.function.Consumer<java.util.List<androidx.window.extensions.embedding.SplitInfo!>!>);
}
public class ActivityRule extends androidx.window.extensions.embedding.EmbeddingRule {
@@ -29,9 +32,11 @@
}
public static final class ActivityRule.Builder {
- ctor public ActivityRule.Builder(java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>);
+ ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.N) public ActivityRule.Builder(java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>);
+ ctor public ActivityRule.Builder(androidx.window.extensions.core.util.function.Predicate<android.app.Activity!>, androidx.window.extensions.core.util.function.Predicate<android.content.Intent!>);
method public androidx.window.extensions.embedding.ActivityRule build();
method public androidx.window.extensions.embedding.ActivityRule.Builder setShouldAlwaysExpand(boolean);
+ method public androidx.window.extensions.embedding.ActivityRule.Builder setTag(String);
}
public class ActivityStack {
@@ -40,12 +45,63 @@
}
public abstract class EmbeddingRule {
+ method public String? getTag();
+ }
+
+ public class SplitAttributes {
+ method @ColorInt public int getAnimationBackgroundColor();
+ method public int getLayoutDirection();
+ method public androidx.window.extensions.embedding.SplitAttributes.SplitType getSplitType();
+ }
+
+ public static final class SplitAttributes.Builder {
+ ctor public SplitAttributes.Builder();
+ method public androidx.window.extensions.embedding.SplitAttributes build();
+ method public androidx.window.extensions.embedding.SplitAttributes.Builder setAnimationBackgroundColor(@ColorInt int);
+ method public androidx.window.extensions.embedding.SplitAttributes.Builder setLayoutDirection(int);
+ method public androidx.window.extensions.embedding.SplitAttributes.Builder setSplitType(androidx.window.extensions.embedding.SplitAttributes.SplitType);
+ }
+
+ public static final class SplitAttributes.LayoutDirection {
+ field public static final int BOTTOM_TO_TOP = 5; // 0x5
+ field public static final int LEFT_TO_RIGHT = 0; // 0x0
+ field public static final int LOCALE = 3; // 0x3
+ field public static final int RIGHT_TO_LEFT = 1; // 0x1
+ field public static final int TOP_TO_BOTTOM = 4; // 0x4
+ }
+
+ public static class SplitAttributes.SplitType {
+ }
+
+ public static final class SplitAttributes.SplitType.ExpandContainersSplitType extends androidx.window.extensions.embedding.SplitAttributes.SplitType {
+ ctor public SplitAttributes.SplitType.ExpandContainersSplitType();
+ }
+
+ public static final class SplitAttributes.SplitType.HingeSplitType extends androidx.window.extensions.embedding.SplitAttributes.SplitType {
+ ctor public SplitAttributes.SplitType.HingeSplitType(androidx.window.extensions.embedding.SplitAttributes.SplitType);
+ method public androidx.window.extensions.embedding.SplitAttributes.SplitType getFallbackSplitType();
+ }
+
+ public static final class SplitAttributes.SplitType.RatioSplitType extends androidx.window.extensions.embedding.SplitAttributes.SplitType {
+ ctor public SplitAttributes.SplitType.RatioSplitType(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float);
+ method @FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) public float getRatio();
+ method public static androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType splitEqually();
+ }
+
+ public class SplitAttributesCalculatorParams {
+ method public boolean areDefaultConstraintsSatisfied();
+ method public androidx.window.extensions.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public android.content.res.Configuration getParentConfiguration();
+ method public androidx.window.extensions.layout.WindowLayoutInfo getParentWindowLayoutInfo();
+ method public android.view.WindowMetrics getParentWindowMetrics();
+ method public String? getSplitRuleTag();
}
public class SplitInfo {
method public androidx.window.extensions.embedding.ActivityStack getPrimaryActivityStack();
method public androidx.window.extensions.embedding.ActivityStack getSecondaryActivityStack();
- method public float getSplitRatio();
+ method public androidx.window.extensions.embedding.SplitAttributes getSplitAttributes();
+ method @Deprecated public float getSplitRatio();
}
public class SplitPairRule extends androidx.window.extensions.embedding.SplitRule {
@@ -57,15 +113,18 @@
}
public static final class SplitPairRule.Builder {
- ctor public SplitPairRule.Builder(java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.app.Activity!>!>, java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.content.Intent!>!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.N) public SplitPairRule.Builder(java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.app.Activity!>!>, java.util.function.Predicate<android.util.Pair<android.app.Activity!,android.content.Intent!>!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor public SplitPairRule.Builder(androidx.window.extensions.core.util.function.Predicate<android.util.Pair<android.app.Activity!,android.app.Activity!>!>, androidx.window.extensions.core.util.function.Predicate<android.util.Pair<android.app.Activity!,android.content.Intent!>!>, androidx.window.extensions.core.util.function.Predicate<android.view.WindowMetrics!>);
method public androidx.window.extensions.embedding.SplitPairRule build();
+ method public androidx.window.extensions.embedding.SplitPairRule.Builder setDefaultSplitAttributes(androidx.window.extensions.embedding.SplitAttributes);
method public androidx.window.extensions.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int);
method public androidx.window.extensions.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int);
- method public androidx.window.extensions.embedding.SplitPairRule.Builder setLayoutDirection(int);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setLayoutDirection(int);
method public androidx.window.extensions.embedding.SplitPairRule.Builder setShouldClearTop(boolean);
method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setShouldFinishPrimaryWithSecondary(boolean);
method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setShouldFinishSecondaryWithPrimary(boolean);
- method public androidx.window.extensions.embedding.SplitPairRule.Builder setSplitRatio(float);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float);
+ method public androidx.window.extensions.embedding.SplitPairRule.Builder setTag(String);
}
public class SplitPlaceholderRule extends androidx.window.extensions.embedding.SplitRule {
@@ -78,19 +137,23 @@
}
public static final class SplitPlaceholderRule.Builder {
- ctor public SplitPlaceholderRule.Builder(android.content.Intent, java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.N) public SplitPlaceholderRule.Builder(android.content.Intent, java.util.function.Predicate<android.app.Activity!>, java.util.function.Predicate<android.content.Intent!>, java.util.function.Predicate<android.view.WindowMetrics!>);
+ ctor public SplitPlaceholderRule.Builder(android.content.Intent, androidx.window.extensions.core.util.function.Predicate<android.app.Activity!>, androidx.window.extensions.core.util.function.Predicate<android.content.Intent!>, androidx.window.extensions.core.util.function.Predicate<android.view.WindowMetrics!>);
method public androidx.window.extensions.embedding.SplitPlaceholderRule build();
+ method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setDefaultSplitAttributes(androidx.window.extensions.embedding.SplitAttributes);
method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int);
method @Deprecated public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithSecondary(int);
- method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int);
- method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setSplitRatio(float);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int);
+ method @Deprecated public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float);
method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setSticky(boolean);
+ method public androidx.window.extensions.embedding.SplitPlaceholderRule.Builder setTag(String);
}
public abstract class SplitRule extends androidx.window.extensions.embedding.EmbeddingRule {
method @RequiresApi(api=android.os.Build.VERSION_CODES.N) public boolean checkParentMetrics(android.view.WindowMetrics);
- method public int getLayoutDirection();
- method public float getSplitRatio();
+ method public androidx.window.extensions.embedding.SplitAttributes getDefaultSplitAttributes();
+ method @Deprecated public int getLayoutDirection();
+ method @Deprecated public float getSplitRatio();
field public static final int FINISH_ADJACENT = 2; // 0x2
field public static final int FINISH_ALWAYS = 1; // 0x1
field public static final int FINISH_NEVER = 0; // 0x0
@@ -116,8 +179,11 @@
}
public interface WindowLayoutComponent {
- method public void addWindowLayoutInfoListener(android.app.Activity, java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
- method public void removeWindowLayoutInfoListener(java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method @Deprecated public void addWindowLayoutInfoListener(android.app.Activity, java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method @Deprecated public default void addWindowLayoutInfoListener(@UiContext android.content.Context, java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method public default void addWindowLayoutInfoListener(@UiContext android.content.Context, androidx.window.extensions.core.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method @Deprecated public void removeWindowLayoutInfoListener(java.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
+ method public default void removeWindowLayoutInfoListener(androidx.window.extensions.core.util.function.Consumer<androidx.window.extensions.layout.WindowLayoutInfo!>);
}
public class WindowLayoutInfo {
diff --git a/window/extensions/extensions/build.gradle b/window/extensions/extensions/build.gradle
index f9deca6..9db659b 100644
--- a/window/extensions/extensions/build.gradle
+++ b/window/extensions/extensions/build.gradle
@@ -24,8 +24,13 @@
dependencies {
api(libs.kotlinStdlib)
- implementation("androidx.annotation:annotation:1.1.0")
+ implementation("androidx.annotation:annotation:1.3.0")
implementation("androidx.annotation:annotation-experimental:1.1.0")
+ implementation("androidx.window.extensions.core:core:1.0.0-alpha01")
+
+ testImplementation(libs.testExtJunit)
+ testImplementation(libs.testRunner)
+ testImplementation(libs.testRules)
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testRunner)
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 c1a9bd8..8b6281a 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
@@ -16,7 +16,10 @@
package androidx.window.extensions;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.layout.WindowLayoutComponent;
@@ -29,6 +32,53 @@
* {@link WindowExtensions#getVendorApiLevel()}.
*/
public interface WindowExtensions {
+ // TODO(b/241323716) Removed after we have annotation to check API level
+ /**
+ * An invalid {@link #getVendorApiLevel vendor API level}
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ int INVALID_VENDOR_API_LEVEL = -1;
+
+ // TODO(b/241323716) Removed after we have annotation to check API level
+ /**
+ * A vendor API level constant. It helps to unify the format of documenting {@code @since}
+ * block.
+ * <p>
+ * The added APIs for Vendor API level 1 are:
+ * <ul>
+ * <li>{@link androidx.window.extensions.embedding.ActivityRule} APIs</li>
+ * <li>{@link androidx.window.extensions.embedding.SplitPairRule} APIs</li>
+ * <li>{@link androidx.window.extensions.embedding.SplitPlaceholderRule} APIs</li>
+ * <li>{@link androidx.window.extensions.embedding.SplitInfo} APIs</li>
+ * <li>{@link androidx.window.extensions.layout.DisplayFeature} APIs</li>
+ * <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>
+ * </ul>
+ * </p>
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ int VENDOR_API_LEVEL_1 = 1;
+ // TODO(b/241323716) Removed after we have annotation to check API level
+ /**
+ * A vendor API level constant. It helps to unify the format of documenting {@code @since}
+ * block.
+ * <p>
+ * The added APIs for Vendor API level 2 are:
+ * <ul>
+ * <li>{@link SplitPlaceholderRule.Builder#setFinishPrimaryWithPlaceholder}</li>
+ * <li>{@link androidx.window.extensions.embedding.SplitAttributes} APIs</li>
+ * <li>{@link ActivityEmbeddingComponent#setSplitAttributesCalculator(
+ * androidx.window.extensions.core.util.function.Function)}</li>
+ * </ul>
+ * </p>
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ int VENDOR_API_LEVEL_2 = 2;
+
/**
* Returns the API level of the vendor library on the device. If the returned version is not
* supported by the WindowManager library, then some functions may not be available or replaced
@@ -39,7 +89,7 @@
* @return the API level supported by the library.
*/
default int getVendorApiLevel() {
- return 1;
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
}
/**
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java
index 16caa61..69bc4fe 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityEmbeddingComponent.java
@@ -17,13 +17,15 @@
package androidx.window.extensions.embedding;
import android.app.Activity;
+import android.view.WindowMetrics;
import androidx.annotation.NonNull;
import androidx.window.extensions.WindowExtensions;
+import androidx.window.extensions.core.util.function.Consumer;
+import androidx.window.extensions.core.util.function.Function;
import java.util.List;
import java.util.Set;
-import java.util.function.Consumer;
/**
* Extension component definition that is used by the WindowManager library to trigger custom
@@ -40,20 +42,36 @@
void setEmbeddingRules(@NonNull Set<EmbeddingRule> splitRules);
/**
+ * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #setSplitInfoCallback(Consumer)} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
+ @SuppressWarnings("ExecutorRegistration") // Jetpack will post it on the app-provided executor.
+ void setSplitInfoCallback(@NonNull java.util.function.Consumer<List<SplitInfo>> consumer);
+
+ /**
* Sets the callback that notifies WM Jetpack about changes in split states from the Extensions
* Sidecar implementation. The listener should be registered for the lifetime of the process.
* There are no threading guarantees where the events are dispatched from. All messages are
* re-posted to the executors provided by developers.
+ *
+ * @param consumer the callback to notify {@link SplitInfo} list changes
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
*/
@SuppressWarnings("ExecutorRegistration") // Jetpack will post it on the app-provided executor.
- void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> consumer);
+ default void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> consumer) {
+ throw new UnsupportedOperationException("This method must not be called unless there is a"
+ + " corresponding override implementation on the device.");
+ }
/**
* Clears the callback that was set in
* {@link ActivityEmbeddingComponent#setSplitInfoCallback(Consumer)}.
* Added in {@link WindowExtensions#getVendorApiLevel()} 2, calling an earlier version will
* throw {@link java.lang.NoSuchMethodError}.
- * @since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2}
+ * since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2}
*/
void clearSplitInfoCallback();
@@ -62,4 +80,27 @@
* occupies a portion of Task bounds.
*/
boolean isActivityEmbedded(@NonNull Activity activity);
+
+ /**
+ * Sets a callback to compute the {@link SplitAttributes} for the {@link SplitRule} and current
+ * window state provided in {@link SplitAttributesCalculatorParams}. This method can be used
+ * to dynamically configure the split layout properties when new activities are launched or
+ * window properties change. If set, {@link SplitRule#getDefaultSplitAttributes() the default
+ * split properties} and {@link SplitRule#checkParentMetrics(WindowMetrics) restrictions}
+ * will be ignored, and the callback will be invoked for every change.
+ *
+ * @param calculator the callback to set. It will replace the previously set callback if it
+ * exists.
+ * since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ void setSplitAttributesCalculator(
+ @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator);
+
+ /**
+ * Clears the previously callback set in {@link #setSplitAttributesCalculator(Function)}.
+ *
+ * @see #setSplitAttributesCalculator(Function)
+ * since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ void clearSplitAttributesCalculator();
}
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java
index 2d1fc33..ed30113 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/ActivityRule.java
@@ -22,9 +22,12 @@
import android.os.Build;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.window.extensions.WindowExtensions;
+import androidx.window.extensions.core.util.function.Predicate;
-import java.util.function.Predicate;
+import java.util.Objects;
/**
* Split configuration rule for individual activities.
@@ -37,7 +40,9 @@
private final boolean mShouldAlwaysExpand;
ActivityRule(@NonNull Predicate<Activity> activityPredicate,
- @NonNull Predicate<Intent> intentPredicate, boolean shouldAlwaysExpand) {
+ @NonNull Predicate<Intent> intentPredicate, boolean shouldAlwaysExpand,
+ @Nullable String tag) {
+ super(tag);
mActivityPredicate = activityPredicate;
mIntentPredicate = intentPredicate;
mShouldAlwaysExpand = shouldAlwaysExpand;
@@ -78,7 +83,32 @@
@NonNull
private final Predicate<Intent> mIntentPredicate;
private boolean mAlwaysExpand;
+ @Nullable
+ private String mTag;
+ /**
+ * @deprecated Use {@link #Builder(Predicate, Predicate)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #Builder(Predicate, Predicate)} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
+ @RequiresApi(Build.VERSION_CODES.N)
+ public Builder(@NonNull java.util.function.Predicate<Activity> activityPredicate,
+ @NonNull java.util.function.Predicate<Intent> intentPredicate) {
+ mActivityPredicate = activityPredicate::test;
+ mIntentPredicate = intentPredicate::test;
+ }
+
+ /**
+ * The {@link ActivityRule} Builder constructor
+ *
+ * @param activityPredicate the {@link Predicate} to verify if a given {@link Activity}
+ * matches the rule
+ * @param intentPredicate the {@link Predicate} to verify if a given {@link Intent}
+ * matches the rule
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
public Builder(@NonNull Predicate<Activity> activityPredicate,
@NonNull Predicate<Intent> intentPredicate) {
mActivityPredicate = activityPredicate;
@@ -92,10 +122,20 @@
return this;
}
+ /**
+ * @see ActivityRule#getTag()
+ * since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @NonNull
+ public Builder setTag(@NonNull String tag) {
+ mTag = Objects.requireNonNull(tag);
+ return this;
+ }
+
/** Builds a new instance of {@link ActivityRule}. */
@NonNull
public ActivityRule build() {
- return new ActivityRule(mActivityPredicate, mIntentPredicate, mAlwaysExpand);
+ return new ActivityRule(mActivityPredicate, mIntentPredicate, mAlwaysExpand, mTag);
}
}
@@ -104,14 +144,16 @@
if (this == o) return true;
if (!(o instanceof ActivityRule)) return false;
ActivityRule that = (ActivityRule) o;
- return mShouldAlwaysExpand == that.mShouldAlwaysExpand
+ return super.equals(o)
+ && mShouldAlwaysExpand == that.mShouldAlwaysExpand
&& mActivityPredicate.equals(that.mActivityPredicate)
&& mIntentPredicate.equals(that.mIntentPredicate);
}
@Override
public int hashCode() {
- int result = mActivityPredicate.hashCode();
+ int result = super.hashCode();
+ result = 31 * result + mActivityPredicate.hashCode();
result = 31 * result + mIntentPredicate.hashCode();
result = 31 * result + (mShouldAlwaysExpand ? 1 : 0);
return result;
@@ -120,6 +162,7 @@
@NonNull
@Override
public String toString() {
- return "ActivityRule{" + "mShouldAlwaysExpand=" + mShouldAlwaysExpand + '}';
+ return "ActivityRule{mTag=" + getTag()
+ + "mShouldAlwaysExpand=" + mShouldAlwaysExpand + '}';
}
}
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java
index 571eda0..71acdd9 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/EmbeddingRule.java
@@ -16,10 +16,48 @@
package androidx.window.extensions.embedding;
+import androidx.annotation.Nullable;
+import androidx.window.extensions.core.util.function.Function;
+
+import java.util.Objects;
+
/**
* Base interface for activity embedding rules. Used to group different types of rules together when
* updating from the core library.
*/
public abstract class EmbeddingRule {
- EmbeddingRule() {}
+ @Nullable
+ private final String mTag;
+
+ EmbeddingRule(@Nullable String tag) {
+ mTag = tag;
+ }
+
+ /**
+ * A unique string to identify this {@link EmbeddingRule}.
+ * The suggested usage is to set the tag in the corresponding rule builder to be able to
+ * differentiate between different rules in {@link SplitAttributes} calculator function. For
+ * example, it can be used to compute the {@link SplitAttributes} for the specific
+ * {@link SplitRule} in the {@link Function} set with
+ * {@link ActivityEmbeddingComponent#setSplitAttributesCalculator(Function)}.
+ *
+ * since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @Nullable
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof EmbeddingRule)) return false;
+ final EmbeddingRule otherRule = (EmbeddingRule) other;
+ return Objects.equals(mTag, otherRule.mTag);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mTag);
+ }
}
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributes.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributes.java
new file mode 100644
index 0000000..92a427a
--- /dev/null
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributes.java
@@ -0,0 +1,559 @@
+/*
+ * 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.extensions.embedding;
+
+import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.BOTTOM_TO_TOP;
+import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.LEFT_TO_RIGHT;
+import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.LOCALE;
+import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.RIGHT_TO_LEFT;
+import static androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.TOP_TO_BOTTOM;
+
+import android.annotation.SuppressLint;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.window.extensions.core.util.function.Function;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Attributes that describe how the parent window (typically the activity task
+ * window) is split between the primary and secondary activity containers,
+ * including:
+ * <ul>
+ * <li>Split type -- Categorizes the split and specifies the sizes of the
+ * primary and secondary activity containers relative to the parent
+ * bounds</li>
+ * <li>Layout direction -- Specifies whether the parent window is split
+ * vertically or horizontally and in which direction the primary and
+ * secondary containers are respectively positioned (left to right,
+ * right to left, top to bottom, and so forth)</li>
+ * <li>Animation background color -- The color of the background during
+ * animation of the split involving this {@code SplitAttributes} object
+ * if the animation requires a background</li>
+ * </ul>
+ *
+ * <p>Attributes can be configured by:
+ * <ul>
+ * <li>Setting the default {@code SplitAttributes} using
+ * {@link SplitPairRule.Builder#setDefaultSplitAttributes} or
+ * {@link SplitPlaceholderRule.Builder#setDefaultSplitAttributes}.</li>
+ * <li>Using {@link ActivityEmbeddingComponent#setSplitAttributesCalculator(Function)} to set
+ * the callback to customize the {@code SplitAttributes} for a given device and window
+ * state.</li>
+ * </ul>
+ *
+ * @see SplitAttributes.SplitType
+ * @see SplitAttributes.LayoutDirection
+ * since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+public class SplitAttributes {
+ /**
+ * The type of window split, which defines the proportion of the parent
+ * window occupied by the primary and secondary activity containers.
+ */
+ public static class SplitType {
+ @NonNull
+ private final String mDescription;
+
+ SplitType(@NonNull String description) {
+ mDescription = description;
+ }
+
+ @Override
+ public int hashCode() {
+ return mDescription.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SplitType)) {
+ return false;
+ }
+ final SplitType that = (SplitType) obj;
+ return mDescription.equals(that.mDescription);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return mDescription;
+ }
+
+ @SuppressLint("Range") // The range is covered.
+ @NonNull
+ static SplitType createSplitTypeFromLegacySplitRatio(
+ @FloatRange(from = 0.0, to = 1.0) float splitRatio) {
+ // Treat 0.0 and 1.0 as ExpandContainerSplitType because it means the parent container
+ // is filled with secondary or primary container.
+ if (splitRatio == 0.0 || splitRatio == 1.0) {
+ return new ExpandContainersSplitType();
+ }
+ return new RatioSplitType(splitRatio);
+ }
+
+ /**
+ * A window split that's based on the ratio of the size of the primary
+ * container to the size of the parent window.
+ *
+ * <p>Values in the non-inclusive range (0.0, 1.0) define the size of
+ * the primary container relative to the size of the parent window:
+ * <ul>
+ * <li>0.5 -- Primary container occupies half of the parent
+ * window; secondary container, the other half</li>
+ * <li>Greater than 0.5 -- Primary container occupies a larger
+ * proportion of the parent window than the secondary
+ * container</li>
+ * <li>Less than 0.5 -- Primary container occupies a smaller
+ * proportion of the parent window than the secondary
+ * container</li>
+ * </ul>
+ */
+ public static final class RatioSplitType extends SplitType {
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
+ private final float mRatio;
+
+ /**
+ * Creates an instance of this {@code RatioSplitType}.
+ *
+ * @param ratio The proportion of the parent window occupied by the
+ * primary container of the split. Can be a value in the
+ * non-inclusive range (0.0, 1.0). Use
+ * {@link SplitType.ExpandContainersSplitType} to create a split
+ * type that occupies the entire parent window.
+ */
+ public RatioSplitType(
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
+ float ratio) {
+ super("ratio:" + ratio);
+ if (ratio <= 0.0f || ratio >= 1.0f) {
+ throw new IllegalArgumentException("Ratio must be in range (0.0, 1.0). "
+ + " Use SplitType.ExpandContainersSplitType() instead of 0 or 1.");
+ }
+ mRatio = ratio;
+ }
+
+ /**
+ * Gets the proportion of the parent window occupied by the primary
+ * activity container of the split.
+ *
+ * @return The proportion of the split occupied by the primary
+ * container.
+ */
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
+ public float getRatio() {
+ return mRatio;
+ }
+
+ /**
+ * Creates a split type in which the primary and secondary
+ * containers occupy equal portions of the parent window.
+ *
+ * Serves as the default {@link SplitType} if
+ * {@link SplitAttributes.Builder#setSplitType(SplitType)} is not
+ * specified.
+ *
+ * @return A {@code RatioSplitType} in which the activity containers
+ * occupy equal portions of the parent window.
+ */
+ @NonNull
+ public static RatioSplitType splitEqually() {
+ return new RatioSplitType(0.5f);
+ }
+ }
+
+ /**
+ * A parent window split in which the split ratio conforms to the
+ * position of a hinge or separating fold in the device display.
+ *
+ * The split type is created only if:
+ * <ul>
+ * <li>The host task is not in multi-window mode (e.g.,
+ * split-screen mode or picture-in-picture mode)</li>
+ * <li>The device has a hinge or separating fold reported by
+ * [androidx.window.layout.FoldingFeature.isSeparating]</li>
+ * <li>The hinge or separating fold orientation matches how the
+ * parent bounds are split:
+ * <ul>
+ * <li>The hinge or fold orientation is vertical, and
+ * the task bounds are also split vertically
+ * (containers are side by side)</li>
+ * <li>The hinge or fold orientation is horizontal, and
+ * the task bounds are also split horizontally
+ * (containers are top and bottom)</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * Otherwise, the type falls back to the {@code SplitType} returned by
+ * {@link #getFallbackSplitType()}.
+ */
+ public static final class HingeSplitType extends SplitType {
+ @NonNull
+ private final SplitType mFallbackSplitType;
+
+ /**
+ * Creates an instance of this {@code HingeSplitType}.
+ *
+ * @param fallbackSplitType The split type to use if a split based
+ * on the device hinge or separating fold cannot be determined.
+ * Can be a {@link RatioSplitType} or
+ * {@link ExpandContainersSplitType}.
+ */
+ public HingeSplitType(@NonNull SplitType fallbackSplitType) {
+ super("hinge, fallbackType=" + fallbackSplitType);
+ mFallbackSplitType = fallbackSplitType;
+ }
+
+ /**
+ * Returns the fallback {@link SplitType} if a split based on the
+ * device hinge or separating fold cannot be determined.
+ */
+ @NonNull
+ public SplitType getFallbackSplitType() {
+ return mFallbackSplitType;
+ }
+ }
+
+ /**
+ * A window split in which the primary and secondary activity containers
+ * each occupy the entire parent window.
+ *
+ * The secondary container overlays the primary container.
+ */
+ public static final class ExpandContainersSplitType extends SplitType {
+
+ /**
+ * Creates an instance of this {@code ExpandContainersSplitType}.
+ */
+ public ExpandContainersSplitType() {
+ super("expandContainers");
+ }
+ }
+ }
+
+ /**
+ * The layout direction of the primary and secondary activity containers.
+ */
+ public static final class LayoutDirection {
+
+ /**
+ * Specifies that the parent bounds are split vertically (side to side).
+ *
+ * Places the primary container in the left portion of the parent
+ * window, and the secondary container in the right portion.
+ *
+ * A possible return value of {@link SplitType#getLayoutDirection()}.
+ */
+ //
+ // -------------------------
+ // | | |
+ // | Primary | Secondary |
+ // | | |
+ // -------------------------
+ //
+ // Must match {@link LayoutDirection#LTR} for backwards compatibility
+ // with prior versions of Extensions.
+ public static final int LEFT_TO_RIGHT = 0;
+
+ /**
+ * Specifies that the parent bounds are split vertically (side to
+ * side).
+ *
+ * Places the primary container in the right portion of the parent
+ * window, and the secondary container in the left portion.
+ *
+ * A possible return value of {@link SplitType#getLayoutDirection()}.
+ */
+ // -------------------------
+ // | | |
+ // | Secondary | Primary |
+ // | | |
+ // -------------------------
+ //
+ // Must match {@link LayoutDirection#RTL} for backwards compatibility
+ // with prior versions of Extensions.
+ public static final int RIGHT_TO_LEFT = 1;
+
+ /**
+ * Specifies that the parent bounds are split vertically (side to side).
+ *
+ * The direction of the primary and secondary containers is deduced from
+ * the locale as either {@link #LEFT_TO_RIGHT} or
+ * {@link #RIGHT_TO_LEFT}.
+ *
+ * A possible return value of {@link SplitType#getLayoutDirection()}.
+ */
+ // Must match {@link LayoutDirection#LOCALE} for backwards
+ // compatibility with prior versions of Extensions.
+ public static final int LOCALE = 3;
+
+ /**
+ * Specifies that the parent bounds are split horizontally (top and
+ * bottom).
+ *
+ * Places the primary container in the top portion of the parent window,
+ * and the secondary container in the bottom portion.
+ *
+ * If the horizontal layout direction is not supported on the device,
+ * layout direction falls back to {@link #LOCALE}.
+ *
+ * A possible return value of {@link SplitType#getLayoutDirection()}.
+ */
+ // -------------
+ // | |
+ // | Primary |
+ // | |
+ // -------------
+ // | |
+ // | Secondary |
+ // | |
+ // -------------
+ public static final int TOP_TO_BOTTOM = 4;
+
+ /**
+ * Specifies that the parent bounds are split horizontally (top and
+ * bottom).
+ *
+ * Places the primary container in the bottom portion of the parent
+ * window, and the secondary container in the top portion.
+ *
+ * If the horizontal layout direction is not supported on the device,
+ * layout direction falls back to {@link #LOCALE}.
+ *
+ * A possible return value of {@link SplitType#getLayoutDirection()}.
+ */
+ // -------------
+ // | |
+ // | Secondary |
+ // | |
+ // -------------
+ // | |
+ // | Primary |
+ // | |
+ // -------------
+ public static final int BOTTOM_TO_TOP = 5;
+
+ private LayoutDirection() {}
+ }
+
+ @IntDef({LEFT_TO_RIGHT, RIGHT_TO_LEFT, LOCALE, TOP_TO_BOTTOM, BOTTOM_TO_TOP})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ExtLayoutDirection {}
+
+ @ExtLayoutDirection
+ private final int mLayoutDirection;
+
+ private final SplitType mSplitType;
+
+ @ColorInt
+ private final int mAnimationBackgroundColor;
+
+ /**
+ * Creates an instance of this {@code SplitAttributes}.
+ *
+ * @param splitType The type of split. See
+ * {@link SplitAttributes.SplitType}.
+ * @param layoutDirection The layout direction of the split, such as left to
+ * right or top to bottom. See {@link SplitAttributes.LayoutDirection}.
+ * @param animationBackgroundColor The {@link ColorInt} to use for the
+ * background color during animation of the split involving this
+ * {@code SplitAttributes} object if the animation requires a
+ * background.
+ */
+ SplitAttributes(@NonNull SplitType splitType, @ExtLayoutDirection int layoutDirection,
+ @ColorInt int animationBackgroundColor) {
+ mSplitType = splitType;
+ mLayoutDirection = layoutDirection;
+ mAnimationBackgroundColor = animationBackgroundColor;
+ }
+
+ /**
+ * Gets the layout direction of the split.
+ *
+ * @return The layout direction of the split.
+ */
+ @ExtLayoutDirection
+ public int getLayoutDirection() {
+ return mLayoutDirection;
+ }
+
+ /**
+ * Gets the split type.
+ *
+ * @return The split type.
+ */
+ @NonNull
+ public SplitType getSplitType() {
+ return mSplitType;
+ }
+
+ /**
+ * Gets the {@link ColorInt} to use for the background color during the
+ * animation of the split involving this {@code SplitAttributes} object.
+ *
+ * @return The animation background {@code ColorInt}.
+ */
+ @ColorInt
+ public int getAnimationBackgroundColor() {
+ return mAnimationBackgroundColor;
+ }
+
+ /**
+ * Builder for creating an instance of {@link SplitAttributes}.
+ *
+ * The default split type is an equal split between primary and secondary
+ * containers. The default layout direction is based on locale. The default
+ * animation background color is 0, which specifies the theme window
+ * background color.
+ */
+ public static final class Builder {
+ @NonNull
+ private SplitType mSplitType = new SplitType.RatioSplitType(0.5f);
+ @ExtLayoutDirection
+ private int mLayoutDirection = LOCALE;
+
+ @ColorInt
+ private int mAnimationBackgroundColor = 0;
+
+ /**
+ * Sets the split type attribute.
+ *
+ * The default is an equal split between primary and secondary
+ * containers (see {@link SplitType.RatioSplitType#splitEqually()}).
+ *
+ * @param splitType The split type attribute.
+ * @return This {@code Builder}.
+ */
+ @NonNull
+ public Builder setSplitType(@NonNull SplitType splitType) {
+ mSplitType = splitType;
+ return this;
+ }
+
+ /**
+ * Sets the split layout direction attribute.
+ *
+ * The default is based on locale.
+ *
+ * Must be one of:
+ * <ul>
+ * <li>{@link LayoutDirection#LOCALE}</li>
+ * <li>{@link LayoutDirection#LEFT_TO_RIGHT}</li>
+ * <li>{@link LayoutDirection#RIGHT_TO_LEFT}</li>
+ * <li>{@link LayoutDirection#TOP_TO_BOTTOM}</li>
+ * <li>{@link LayoutDirection#BOTTOM_TO_TOP}</li>
+ * </ul>
+ *
+ * @param layoutDirection The layout direction attribute.
+ * @return This {@code Builder}.
+ */
+ @SuppressLint("WrongConstant") // To be compat with android.util.LayoutDirection APIs
+ @NonNull
+ public Builder setLayoutDirection(@ExtLayoutDirection int layoutDirection) {
+ mLayoutDirection = layoutDirection;
+ return this;
+ }
+
+ /**
+ * Sets the {@link ColorInt} to use for the background during the
+ * animation of the split involving this {@code SplitAttributes} object
+ * if the animation requires a background.
+ *
+ * The default value is 0, which specifies the theme window background
+ * color.
+ *
+ * @param color A packed color int of the form {@code AARRGGBB} for the
+ * animation background color.
+ * @return This {@code Builder}.
+ */
+ @NonNull
+ public Builder setAnimationBackgroundColor(@ColorInt int color) {
+ mAnimationBackgroundColor = color;
+ return this;
+ }
+
+ /**
+ * Builds a {@link SplitAttributes} instance with the attributes
+ * specified by {@link #setSplitType}, {@link #setLayoutDirection}, and
+ * {@link #setAnimationBackgroundColor}.
+ *
+ * @return The new {@code SplitAttributes} instance.
+ */
+ @NonNull
+ public SplitAttributes build() {
+ return new SplitAttributes(mSplitType, mLayoutDirection, mAnimationBackgroundColor);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mSplitType.hashCode();
+ result = result * 31 + mLayoutDirection;
+ result = result * 31 + mAnimationBackgroundColor;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof SplitAttributes)) {
+ return false;
+ }
+ final SplitAttributes otherAttributes = (SplitAttributes) other;
+ return mLayoutDirection == otherAttributes.mLayoutDirection
+ && mSplitType.equals(otherAttributes.mSplitType)
+ && mAnimationBackgroundColor == otherAttributes.mAnimationBackgroundColor;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return SplitAttributes.class.getSimpleName() + "{"
+ + "layoutDir=" + layoutDirectionToString()
+ + ", ratio=" + mSplitType
+ + ", animationBgColor=" + Integer.toHexString(mAnimationBackgroundColor)
+ + "}";
+ }
+
+ @NonNull
+ private String layoutDirectionToString() {
+ switch(mLayoutDirection) {
+ case LEFT_TO_RIGHT:
+ return "LEFT_TO_RIGHT";
+ case RIGHT_TO_LEFT:
+ return "RIGHT_TO_LEFT";
+ case LOCALE:
+ return "LOCALE";
+ case TOP_TO_BOTTOM:
+ return "TOP_TO_BOTTOM";
+ case BOTTOM_TO_TOP:
+ return "BOTTOM_TO_TOP";
+ default:
+ throw new IllegalArgumentException("Invalid layout direction:" + mLayoutDirection);
+ }
+ }
+}
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributesCalculatorParams.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributesCalculatorParams.java
new file mode 100644
index 0000000..ad7aa1b
--- /dev/null
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitAttributesCalculatorParams.java
@@ -0,0 +1,149 @@
+/*
+ * 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.extensions.embedding;
+
+import android.content.res.Configuration;
+import android.os.Build;
+import android.view.WindowMetrics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+/**
+ * The parameter container used to report the current device and window state in
+ * {@link ActivityEmbeddingComponent#setSplitAttributesCalculator(
+ * androidx.window.extensions.core.util.function.Function)} and references the corresponding
+ * {@link SplitRule} by {@link #getSplitRuleTag()} if {@link SplitRule#getTag()} is specified.
+ *
+ * @see ActivityEmbeddingComponent#clearSplitAttributesCalculator()
+ * since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+public class SplitAttributesCalculatorParams {
+ @NonNull
+ private final WindowMetrics mParentWindowMetrics;
+ @NonNull
+ private final Configuration mParentConfiguration;
+ @NonNull
+ private final WindowLayoutInfo mParentWindowLayoutInfo;
+ @NonNull
+ private final SplitAttributes mDefaultSplitAttributes;
+ private final boolean mAreDefaultConstraintsSatisfied;
+ @Nullable
+ private final String mSplitRuleTag;
+
+ /** Returns the parent container's {@link WindowMetrics} */
+ @NonNull
+ public WindowMetrics getParentWindowMetrics() {
+ return mParentWindowMetrics;
+ }
+
+ /** Returns the parent container's {@link Configuration} */
+ @NonNull
+ public Configuration getParentConfiguration() {
+ return new Configuration(mParentConfiguration);
+ }
+
+ /**
+ * Returns the {@link SplitRule#getDefaultSplitAttributes()}. It could be from
+ * {@link SplitRule} Builder APIs
+ * ({@link SplitPairRule.Builder#setDefaultSplitAttributes(SplitAttributes)} or
+ * {@link SplitPlaceholderRule.Builder#setDefaultSplitAttributes(SplitAttributes)}) or from
+ * the {@code splitRatio} and {@code splitLayoutDirection} attributes from static rule
+ * definitions.
+ */
+ @NonNull
+ public SplitAttributes getDefaultSplitAttributes() {
+ return mDefaultSplitAttributes;
+ }
+
+ /**
+ * Returns whether the {@link #getParentWindowMetrics()} satisfies the dimensions and aspect
+ * ratios requirements specified in the {@link androidx.window.embedding.SplitRule}, which
+ * are:
+ * - {@link androidx.window.embedding.SplitRule#minWidthDp}
+ * - {@link androidx.window.embedding.SplitRule#minHeightDp}
+ * - {@link androidx.window.embedding.SplitRule#minSmallestWidthDp}
+ * - {@link androidx.window.embedding.SplitRule#maxAspectRatioInPortrait}
+ * - {@link androidx.window.embedding.SplitRule#maxAspectRatioInLandscape}
+ */
+ public boolean areDefaultConstraintsSatisfied() {
+ return mAreDefaultConstraintsSatisfied;
+ }
+
+ /** Returns the parent container's {@link WindowLayoutInfo} */
+ @NonNull
+ public WindowLayoutInfo getParentWindowLayoutInfo() {
+ return mParentWindowLayoutInfo;
+ }
+
+ /**
+ * Returns {@link SplitRule#getTag()} to apply the {@link SplitAttributes} result if it was
+ * set.
+ */
+ @Nullable
+ public String getSplitRuleTag() {
+ return mSplitRuleTag;
+ }
+
+ SplitAttributesCalculatorParams(
+ @NonNull WindowMetrics parentWindowMetrics,
+ @NonNull Configuration parentConfiguration,
+ @NonNull WindowLayoutInfo parentWindowLayoutInfo,
+ @NonNull SplitAttributes defaultSplitAttributes,
+ boolean areDefaultConstraintsSatisfied,
+ @Nullable String splitRuleTag
+ ) {
+ mParentWindowMetrics = parentWindowMetrics;
+ mParentConfiguration = parentConfiguration;
+ mParentWindowLayoutInfo = parentWindowLayoutInfo;
+ mDefaultSplitAttributes = defaultSplitAttributes;
+ mAreDefaultConstraintsSatisfied = areDefaultConstraintsSatisfied;
+ mSplitRuleTag = splitRuleTag;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + ":{"
+ + "windowMetrics=" + windowMetricsToString(mParentWindowMetrics)
+ + ", configuration=" + mParentConfiguration
+ + ", windowLayoutInfo=" + mParentWindowLayoutInfo
+ + ", defaultSplitAttributes=" + mDefaultSplitAttributes
+ + ", areDefaultConstraintsSatisfied=" + mAreDefaultConstraintsSatisfied
+ + ", tag=" + mSplitRuleTag + "}";
+ }
+
+ private static String windowMetricsToString(@NonNull WindowMetrics windowMetrics) {
+ // TODO(b/187712731): Use WindowMetrics#toString after it's implemented in U.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ return Api30Impl.windowMetricsToString(windowMetrics);
+ }
+ throw new UnsupportedOperationException("WindowMetrics didn't exist in R.");
+ }
+
+ @RequiresApi(30)
+ private static final class Api30Impl {
+ static String windowMetricsToString(@NonNull WindowMetrics windowMetrics) {
+ return WindowMetrics.class.getSimpleName() + ":{"
+ + "bounds=" + windowMetrics.getBounds()
+ + ", windowInsets=" + windowMetrics.getWindowInsets()
+ + "}";
+ }
+ }
+}
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java
index a897e2f..9b1b24f 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitInfo.java
@@ -17,6 +17,8 @@
package androidx.window.extensions.embedding;
import androidx.annotation.NonNull;
+import androidx.window.extensions.WindowExtensions;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
/** Describes a split of two containers with activities. */
public class SplitInfo {
@@ -24,13 +26,15 @@
private final ActivityStack mPrimaryActivityStack;
@NonNull
private final ActivityStack mSecondaryActivityStack;
- private final float mSplitRatio;
+ @NonNull
+ private final SplitAttributes mSplitAttributes;
SplitInfo(@NonNull ActivityStack primaryActivityStack,
- @NonNull ActivityStack secondaryActivityStack, float splitRatio) {
+ @NonNull ActivityStack secondaryActivityStack,
+ @NonNull SplitAttributes splitAttributes) {
mPrimaryActivityStack = primaryActivityStack;
mSecondaryActivityStack = secondaryActivityStack;
- mSplitRatio = splitRatio;
+ mSplitAttributes = splitAttributes;
}
@NonNull
@@ -43,8 +47,28 @@
return mSecondaryActivityStack;
}
+ /**
+ * @deprecated Use {@link #getSplitAttributes()} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if {@link #getSplitAttributes()}
+ * can't be called on {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
public float getSplitRatio() {
- return mSplitRatio;
+ final SplitType splitType = mSplitAttributes.getSplitType();
+ if (splitType instanceof SplitType.RatioSplitType) {
+ return ((SplitType.RatioSplitType) splitType).getRatio();
+ } else { // Fallback to use 0.0 because the WM Jetpack may not support HingeSplitType.
+ return 0.0f;
+ }
+ }
+
+ /**
+ * Returns the {@link SplitAttributes} of this split.
+ * since {@link androidx.window.extensions.WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @NonNull
+ public SplitAttributes getSplitAttributes() {
+ return mSplitAttributes;
}
@Override
@@ -52,7 +76,7 @@
if (this == o) return true;
if (!(o instanceof SplitInfo)) return false;
SplitInfo that = (SplitInfo) o;
- return Float.compare(that.mSplitRatio, mSplitRatio) == 0 && mPrimaryActivityStack.equals(
+ return mSplitAttributes.equals(that.mSplitAttributes) && mPrimaryActivityStack.equals(
that.mPrimaryActivityStack) && mSecondaryActivityStack.equals(
that.mSecondaryActivityStack);
}
@@ -61,7 +85,7 @@
public int hashCode() {
int result = mPrimaryActivityStack.hashCode();
result = result * 31 + mSecondaryActivityStack.hashCode();
- result = result * 31 + (int) (mSplitRatio * 17);
+ result = result * 31 + mSplitAttributes.hashCode();
return result;
}
@@ -71,7 +95,7 @@
return "SplitInfo{"
+ "mPrimaryActivityStack=" + mPrimaryActivityStack
+ ", mSecondaryActivityStack=" + mSecondaryActivityStack
- + ", mSplitRatio=" + mSplitRatio
+ + ", mSplitAttributes=" + mSplitAttributes
+ '}';
}
}
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java
index d2e5209..1140790 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPairRule.java
@@ -16,6 +16,8 @@
package androidx.window.extensions.embedding;
+import static androidx.window.extensions.embedding.SplitAttributes.SplitType.createSplitTypeFromLegacySplitRatio;
+
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
@@ -23,10 +25,14 @@
import android.util.Pair;
import android.view.WindowMetrics;
+import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.window.extensions.WindowExtensions;
+import androidx.window.extensions.core.util.function.Predicate;
-import java.util.function.Predicate;
+import java.util.Objects;
/**
* Split configuration rules for activity pairs.
@@ -42,13 +48,14 @@
private final int mFinishSecondaryWithPrimary;
private final boolean mClearTop;
- SplitPairRule(float splitRatio, @LayoutDirection int layoutDirection,
+ SplitPairRule(@NonNull SplitAttributes defaultSplitAttributes,
@SplitFinishBehavior int finishPrimaryWithSecondary,
@SplitFinishBehavior int finishSecondaryWithPrimary, boolean clearTop,
@NonNull Predicate<Pair<Activity, Activity>> activityPairPredicate,
@NonNull Predicate<Pair<Activity, Intent>> activityIntentPredicate,
- @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) {
- super(parentWindowMetricsPredicate, splitRatio, layoutDirection);
+ @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate,
+ @Nullable String tag) {
+ super(parentWindowMetricsPredicate, defaultSplitAttributes, tag);
mActivityPairPredicate = activityPairPredicate;
mActivityIntentPredicate = activityIntentPredicate;
mFinishPrimaryWithSecondary = finishPrimaryWithSecondary;
@@ -114,15 +121,52 @@
private final Predicate<Pair<Activity, Intent>> mActivityIntentPredicate;
@NonNull
private final Predicate<WindowMetrics> mParentWindowMetricsPredicate;
+ // Keep for backward compatibility
+ @FloatRange(from = 0.0, to = 1.0)
private float mSplitRatio;
- @LayoutDirection
+ // Keep for backward compatibility
+ @SplitAttributes.ExtLayoutDirection
private int mLayoutDirection;
+ private SplitAttributes mDefaultSplitAttributes;
private boolean mClearTop;
@SplitFinishBehavior
private int mFinishPrimaryWithSecondary;
@SplitFinishBehavior
private int mFinishSecondaryWithPrimary;
+ @Nullable
+ private String mTag;
+ /**
+ * @deprecated Use {@link #Builder(Predicate, Predicate, Predicate)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #Builder(Predicate, Predicate, Predicate)} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
+ @RequiresApi(Build.VERSION_CODES.N)
+ public Builder(@NonNull java.util.function.Predicate<Pair<Activity, Activity>>
+ activityPairPredicate,
+ @NonNull java.util.function.Predicate<Pair<Activity, Intent>>
+ activityIntentPredicate,
+ @NonNull java.util.function.Predicate<WindowMetrics>
+ parentWindowMetricsPredicate) {
+ mActivityPairPredicate = activityPairPredicate::test;
+ mActivityIntentPredicate = activityIntentPredicate::test;
+ mParentWindowMetricsPredicate = parentWindowMetricsPredicate::test;
+ }
+
+ /**
+ * The {@link SplitPairRule} builder constructor
+ *
+ * @param activityPairPredicate the {@link Predicate} to verify if an {@link Activity} pair
+ * matches this rule
+ * @param activityIntentPredicate the {@link Predicate} to verify if an ({@link Activity},
+ * {@link Intent}) pair matches this rule
+ * @param parentWindowMetricsPredicate the {@link Predicate} to verify if the matched split
+ * pair is allowed to show adjacent to each other with the
+ * given parent {@link WindowMetrics}
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
public Builder(@NonNull Predicate<Pair<Activity, Activity>> activityPairPredicate,
@NonNull Predicate<Pair<Activity, Intent>> activityIntentPredicate,
@NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) {
@@ -131,20 +175,48 @@
mParentWindowMetricsPredicate = parentWindowMetricsPredicate;
}
- /** @see SplitRule#getSplitRatio() */
+ /**
+ * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #setDefaultSplitAttributes(SplitAttributes)} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}. {@code splitRatio} will be translated to
+ * {@link SplitAttributes.SplitType.ExpandContainersSplitType} for value {@code 0.0} and
+ * {@code 1.0}, and {@link SplitAttributes.SplitType.RatioSplitType} for value with range
+ * (0.0, 1.0).
+ */
+ @Deprecated
@NonNull
- public Builder setSplitRatio(float splitRatio) {
+ public Builder setSplitRatio(@FloatRange(from = 0.0, to = 1.0) float splitRatio) {
mSplitRatio = splitRatio;
return this;
}
- /** @see SplitRule#getLayoutDirection() */
+ /**
+ * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #setDefaultSplitAttributes(SplitAttributes)} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
@NonNull
- public Builder setLayoutDirection(@LayoutDirection int layoutDirection) {
+ public Builder setLayoutDirection(@SplitAttributes.ExtLayoutDirection int layoutDirection) {
mLayoutDirection = layoutDirection;
return this;
}
+ /**
+ * See {@link SplitPairRule#getDefaultSplitAttributes()} for reference.
+ * Overrides values if set in {@link #setSplitRatio(float)} and
+ * {@link #setLayoutDirection(int)}
+ *
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @NonNull
+ public Builder setDefaultSplitAttributes(@NonNull SplitAttributes attrs) {
+ mDefaultSplitAttributes = attrs;
+ return this;
+ }
+
/** @deprecated To be removed with next developer preview. */
@Deprecated
@NonNull
@@ -181,13 +253,31 @@
return this;
}
+ /**
+ * @see SplitPairRule#getTag()
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @NonNull
+ public Builder setTag(@NonNull String tag) {
+ mTag = Objects.requireNonNull(tag);
+ return this;
+ }
+
/** Builds a new instance of {@link SplitPairRule}. */
@NonNull
public SplitPairRule build() {
- return new SplitPairRule(mSplitRatio, mLayoutDirection,
+ // To provide compatibility with prior version of WM Jetpack library, where
+ // #setDefaultAttributes hasn't yet been supported and thus would not be set.
+ mDefaultSplitAttributes = (mDefaultSplitAttributes != null)
+ ? mDefaultSplitAttributes
+ : new SplitAttributes.Builder()
+ .setSplitType(createSplitTypeFromLegacySplitRatio(mSplitRatio))
+ .setLayoutDirection(mLayoutDirection)
+ .build();
+ return new SplitPairRule(mDefaultSplitAttributes,
mFinishPrimaryWithSecondary, mFinishSecondaryWithPrimary,
mClearTop, mActivityPairPredicate, mActivityIntentPredicate,
- mParentWindowMetricsPredicate);
+ mParentWindowMetricsPredicate, mTag);
}
}
@@ -219,7 +309,9 @@
@Override
public String toString() {
return "SplitPairRule{"
- + "mFinishPrimaryWithSecondary=" + mFinishPrimaryWithSecondary
+ + "mTag=" + getTag()
+ + ", mDefaultSplitAttributes=" + getDefaultSplitAttributes()
+ + ", mFinishPrimaryWithSecondary=" + mFinishPrimaryWithSecondary
+ ", mFinishSecondaryWithPrimary=" + mFinishSecondaryWithPrimary
+ ", mClearTop=" + mClearTop
+ '}';
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java
index 7b30849..e6cfed8 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitPlaceholderRule.java
@@ -16,19 +16,25 @@
package androidx.window.extensions.embedding;
+import static androidx.window.extensions.embedding.SplitAttributes.SplitType.createSplitTypeFromLegacySplitRatio;
+
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.view.WindowMetrics;
+import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.window.extensions.WindowExtensions;
+import androidx.window.extensions.core.util.function.Predicate;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.function.Predicate;
+import java.util.Objects;
/**
* Split configuration rules for split placeholders - activities used to occupy additional
@@ -58,12 +64,14 @@
private final int mFinishPrimaryWithPlaceholder;
SplitPlaceholderRule(@NonNull Intent placeholderIntent,
- float splitRatio, @LayoutDirection int layoutDirection, boolean isSticky,
+ @NonNull SplitAttributes defaultSplitAttributes,
+ boolean isSticky,
@SplitPlaceholderFinishBehavior int finishPrimaryWithPlaceholder,
@NonNull Predicate<Activity> activityPredicate,
@NonNull Predicate<Intent> intentPredicate,
- @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate) {
- super(parentWindowMetricsPredicate, splitRatio, layoutDirection);
+ @NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate,
+ @Nullable String tag) {
+ super(parentWindowMetricsPredicate, defaultSplitAttributes, tag);
mIsSticky = isSticky;
mFinishPrimaryWithPlaceholder = finishPrimaryWithPlaceholder;
mActivityPredicate = activityPredicate;
@@ -106,7 +114,10 @@
}
/**
- * @deprecated Use {@link #getFinishPrimaryWithPlaceholder()} instead.
+ * @deprecated Use {@link #getFinishPrimaryWithPlaceholder()} instead starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #getFinishPrimaryWithPlaceholder()} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
*/
@Deprecated
@SplitPlaceholderFinishBehavior
@@ -117,8 +128,10 @@
/**
* Determines what happens with the primary container when all activities are finished in the
* associated secondary/placeholder container.
- * TODO(b/238905747): Add api guard for extensions.
+ *
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
*/
+ // TODO(b/238905747): Add api guard for extensions.
@SplitPlaceholderFinishBehavior
public int getFinishPrimaryWithPlaceholder() {
return mFinishPrimaryWithPlaceholder;
@@ -136,13 +149,51 @@
private final Predicate<WindowMetrics> mParentWindowMetricsPredicate;
@NonNull
private final Intent mPlaceholderIntent;
+ // Keep for backward compatibility
+ @FloatRange(from = 0.0, to = 1.0)
private float mSplitRatio;
- @LayoutDirection
+ // Keep for backward compatibility
+ @SplitAttributes.ExtLayoutDirection
private int mLayoutDirection;
+ private SplitAttributes mDefaultSplitAttributes;
private boolean mIsSticky = false;
@SplitPlaceholderFinishBehavior
private int mFinishPrimaryWithPlaceholder = FINISH_ALWAYS;
+ @Nullable
+ private String mTag;
+ /**
+ * @deprecated Use {@link #Builder(Intent, Predicate, Predicate, Predicate)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #Builder(Intent, Predicate, Predicate, Predicate)} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
+ @RequiresApi(Build.VERSION_CODES.N)
+ public Builder(@NonNull Intent placeholderIntent,
+ @NonNull java.util.function.Predicate<Activity> activityPredicate,
+ @NonNull java.util.function.Predicate<Intent> intentPredicate,
+ @NonNull java.util.function.Predicate<WindowMetrics> parentWindowMetricsPredicate) {
+ mActivityPredicate = activityPredicate::test;
+ mIntentPredicate = intentPredicate::test;
+ mPlaceholderIntent = placeholderIntent;
+ mParentWindowMetricsPredicate = parentWindowMetricsPredicate::test;
+ }
+
+ /**
+ * The {@link SplitPlaceholderRule} Builder constructor
+ * @param placeholderIntent the placeholder activity to launch if
+ * {@link SplitPlaceholderRule#checkParentMetrics(WindowMetrics)}
+ * is satisfied
+ * @param activityPredicate the {@link Predicate} to verify if a given {@link Activity}
+ * matches the rule
+ * @param intentPredicate the {@link Predicate} to verify if a given {@link Intent}
+ * matches the rule
+ * @param parentWindowMetricsPredicate the {@link Predicate} to verify if the placeholder
+ * {@link Activity} should be launched with the given
+ * {@link WindowMetrics}
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
public Builder(@NonNull Intent placeholderIntent,
@NonNull Predicate<Activity> activityPredicate,
@NonNull Predicate<Intent> intentPredicate,
@@ -153,20 +204,48 @@
mParentWindowMetricsPredicate = parentWindowMetricsPredicate;
}
- /** @see SplitRule#getSplitRatio() */
+ /**
+ * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #setDefaultSplitAttributes(SplitAttributes)} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}. {@code splitRatio} will be translated to
+ * @link SplitAttributes.SplitType.ExpandContainersSplitType} for value
+ * {@code 0.0} and {@code 1.0}, and {@link SplitAttributes.SplitType.RatioSplitType} for
+ * value with range (0.0, 1.0).
+ */
+ @Deprecated
@NonNull
- public Builder setSplitRatio(float splitRatio) {
+ public Builder setSplitRatio(@FloatRange(from = 0.0, to = 1.0) float splitRatio) {
mSplitRatio = splitRatio;
return this;
}
- /** @see SplitRule#getLayoutDirection() */
+ /**
+ * @deprecated Use {@link #setDefaultSplitAttributes(SplitAttributes)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #setDefaultSplitAttributes(SplitAttributes)} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
@NonNull
- public Builder setLayoutDirection(@LayoutDirection int layoutDirection) {
+ public Builder setLayoutDirection(@SplitAttributes.ExtLayoutDirection int layoutDirection) {
mLayoutDirection = layoutDirection;
return this;
}
+ /**
+ * See {@link SplitPlaceholderRule#getDefaultSplitAttributes()} for reference.
+ * Overrides values if set in {@link #setSplitRatio(float)} and
+ * {@link #setLayoutDirection(int)}
+ *
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @NonNull
+ public Builder setDefaultSplitAttributes(@NonNull SplitAttributes attrs) {
+ mDefaultSplitAttributes = attrs;
+ return this;
+ }
+
/** @see SplitPlaceholderRule#isSticky() */
@NonNull
public Builder setSticky(boolean sticky) {
@@ -175,7 +254,8 @@
}
/**
- * @deprecated Use SplitPlaceholderRule#setFinishPrimaryWithPlaceholder(int)} instead.
+ * @deprecated Use SplitPlaceholderRule#setFinishPrimaryWithPlaceholder(int)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}.
*/
@Deprecated
@NonNull
@@ -189,8 +269,9 @@
/**
* @see SplitPlaceholderRule#getFinishPrimaryWithPlaceholder()
- * TODO(b/238905747): Add api guard for extensions.
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
*/
+ // TODO(b/238905747): Add api guard for extensions.
@NonNull
public Builder setFinishPrimaryWithPlaceholder(
@SplitPlaceholderFinishBehavior int finishBehavior) {
@@ -198,12 +279,30 @@
return this;
}
+ /**
+ * @see SplitPlaceholderRule#getTag()
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @NonNull
+ public Builder setTag(@NonNull String tag) {
+ mTag = Objects.requireNonNull(tag);
+ return this;
+ }
+
/** Builds a new instance of {@link SplitPlaceholderRule}. */
@NonNull
public SplitPlaceholderRule build() {
- return new SplitPlaceholderRule(mPlaceholderIntent, mSplitRatio,
- mLayoutDirection, mIsSticky, mFinishPrimaryWithPlaceholder, mActivityPredicate,
- mIntentPredicate, mParentWindowMetricsPredicate);
+ // To provide compatibility with prior version of WM Jetpack library, where
+ // #setDefaultAttributes hasn't yet been supported and thus would not be set.
+ mDefaultSplitAttributes = (mDefaultSplitAttributes != null)
+ ? mDefaultSplitAttributes
+ : new SplitAttributes.Builder()
+ .setSplitType(createSplitTypeFromLegacySplitRatio(mSplitRatio))
+ .setLayoutDirection(mLayoutDirection)
+ .build();
+ return new SplitPlaceholderRule(mPlaceholderIntent, mDefaultSplitAttributes, mIsSticky,
+ mFinishPrimaryWithPlaceholder, mActivityPredicate,
+ mIntentPredicate, mParentWindowMetricsPredicate, mTag);
}
}
@@ -237,7 +336,9 @@
@Override
public String toString() {
return "SplitPlaceholderRule{"
- + "mActivityPredicate=" + mActivityPredicate
+ + "mTag=" + getTag()
+ + ", mDefaultSplitAttributes=" + getDefaultSplitAttributes()
+ + ", mActivityPredicate=" + mActivityPredicate
+ ", mIsSticky=" + mIsSticky
+ ", mFinishPrimaryWithPlaceholder=" + mFinishPrimaryWithPlaceholder
+ '}';
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java
index bb24318..171df7b 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/embedding/SplitRule.java
@@ -16,39 +16,36 @@
package androidx.window.extensions.embedding;
-import static android.util.LayoutDirection.LOCALE;
-import static android.util.LayoutDirection.LTR;
-import static android.util.LayoutDirection.RTL;
-
import android.annotation.SuppressLint;
import android.os.Build;
import android.view.WindowMetrics;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.window.extensions.WindowExtensions;
+import androidx.window.extensions.core.util.function.Predicate;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.function.Predicate;
+import java.util.Objects;
/**
* Split configuration rules for activities that are launched to side in a split. Define when an
* activity that was launched in a side container from another activity should be shown
- * side-by-side or on top of it, as well as the visual properties of the split. Can be applied to
+ * adjacent or on top of it, as well as the visual properties of the split. Can be applied to
* new activities started from the same process automatically by the embedding implementation on
* the device.
*/
public abstract class SplitRule extends EmbeddingRule {
@NonNull
private final Predicate<WindowMetrics> mParentWindowMetricsPredicate;
- private final float mSplitRatio;
- @LayoutDirection
- private final int mLayoutDirection;
- @IntDef({LTR, RTL, LOCALE})
- @Retention(RetentionPolicy.SOURCE)
- @interface LayoutDirection {}
+ @NonNull
+ private final SplitAttributes mDefaultSplitAttributes;
+
/**
* Never finish the associated container.
* @see SplitFinishBehavior
@@ -60,9 +57,8 @@
*/
public static final int FINISH_ALWAYS = 1;
/**
- * Only finish the associated container when displayed side-by-side/adjacent to the one
- * being finished. Does not finish the associated one when containers are stacked on top of
- * each other.
+ * Only finish the associated container when displayed adjacent to the one being finished. Does
+ * not finish the associated one when containers are stacked on top of each other.
* @see SplitFinishBehavior
*/
public static final int FINISH_ADJACENT = 2;
@@ -73,7 +69,7 @@
* <p>
* For example, given that {@link SplitPairRule#getFinishPrimaryWithSecondary()} is
* {@link #FINISH_ADJACENT} and secondary container finishes. The primary associated
- * container is finished if it's side-by-side with secondary container. The primary
+ * container is finished if it's shown adjacent to the secondary container. The primary
* associated container is not finished if it occupies entire task bounds.</p>
*
* @see SplitPairRule#getFinishPrimaryWithSecondary()
@@ -88,15 +84,24 @@
@Retention(RetentionPolicy.SOURCE)
@interface SplitFinishBehavior {}
- SplitRule(@NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate, float splitRatio,
- @LayoutDirection int layoutDirection) {
+ SplitRule(@NonNull Predicate<WindowMetrics> parentWindowMetricsPredicate,
+ @NonNull SplitAttributes defaultSplitAttributes, @Nullable String tag) {
+ super(tag);
mParentWindowMetricsPredicate = parentWindowMetricsPredicate;
- mSplitRatio = splitRatio;
- mLayoutDirection = layoutDirection;
+ mDefaultSplitAttributes = defaultSplitAttributes;
}
/**
- * Verifies if the provided parent bounds allow to show the split containers side by side.
+ * Checks whether the parent window satisfied the dimensions and aspect ratios requirements
+ * specified in the {@link androidx.window.embedding.SplitRule}, which are
+ * {@link androidx.window.embedding.SplitRule#minWidthDp},
+ * {@link androidx.window.embedding.SplitRule#minHeightDp},
+ * {@link androidx.window.embedding.SplitRule#minSmallestWidthDp},
+ * {@link androidx.window.embedding.SplitRule#maxAspectRatioInPortrait} and
+ * {@link androidx.window.embedding.SplitRule#maxAspectRatioInLandscape}.
+ *
+ * @param parentMetrics the {@link WindowMetrics} of the parent window.
+ * @return whether the parent window satisfied the {@link SplitRule} requirements.
*/
@SuppressLint("ClassVerificationFailure") // Only called by Extensions implementation on device.
@RequiresApi(api = Build.VERSION_CODES.N)
@@ -104,13 +109,43 @@
return mParentWindowMetricsPredicate.test(parentMetrics);
}
+ /**
+ * @deprecated Use {@link #getDefaultSplitAttributes()} instead starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #getDefaultSplitAttributes()} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
public float getSplitRatio() {
- return mSplitRatio;
+ final SplitType splitType = mDefaultSplitAttributes.getSplitType();
+ if (splitType instanceof SplitType.RatioSplitType) {
+ return ((SplitType.RatioSplitType) splitType).getRatio();
+ } else { // Fallback to use 0.0 because the WM Jetpack may not support HingeSplitType.
+ return 0.0f;
+ }
}
- @LayoutDirection
+ /**
+ * @deprecated Use {@link #getDefaultSplitAttributes()} instead starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #getDefaultSplitAttributes()} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
+ @SplitAttributes.ExtLayoutDirection
public int getLayoutDirection() {
- return mLayoutDirection;
+ return mDefaultSplitAttributes.getLayoutDirection();
+ }
+
+ /**
+ * Returns the default {@link SplitAttributes} which is applied if
+ * {@link #checkParentMetrics(WindowMetrics)} is {@code true}.
+ *
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @NonNull
+ public SplitAttributes getDefaultSplitAttributes() {
+ return mDefaultSplitAttributes;
}
@Override
@@ -118,16 +153,16 @@
if (this == o) return true;
if (!(o instanceof SplitRule)) return false;
SplitRule that = (SplitRule) o;
- return Float.compare(that.mSplitRatio, mSplitRatio) == 0
- && mParentWindowMetricsPredicate.equals(that.mParentWindowMetricsPredicate)
- && mLayoutDirection == that.mLayoutDirection;
+ return super.equals(that)
+ && mDefaultSplitAttributes.equals(that.mDefaultSplitAttributes)
+ && mParentWindowMetricsPredicate.equals(that.mParentWindowMetricsPredicate);
}
@Override
public int hashCode() {
- int result = (int) (mSplitRatio * 17);
+ int result = super.hashCode();
result = 31 * result + mParentWindowMetricsPredicate.hashCode();
- result = 31 * result + mLayoutDirection;
+ result = 31 * result + Objects.hashCode(mDefaultSplitAttributes);
return result;
}
@@ -135,8 +170,8 @@
@Override
public String toString() {
return "SplitRule{"
- + "mSplitRatio=" + mSplitRatio
- + ", mLayoutDirection=" + mLayoutDirection
+ + "mTag=" + getTag()
+ + ", mDefaultSplitAttributes=" + mDefaultSplitAttributes
+ '}';
}
}
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java
index 3b6dd1a..d24f981 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/layout/WindowLayoutComponent.java
@@ -17,18 +17,19 @@
package androidx.window.extensions.layout;
import android.app.Activity;
+import android.content.Context;
import androidx.annotation.NonNull;
+import androidx.annotation.UiContext;
import androidx.window.extensions.WindowExtensions;
-
-import java.util.function.Consumer;
+import androidx.window.extensions.core.util.function.Consumer;
/**
* The interface definition that will be used by the WindowManager library to get custom
* OEM-provided information about the window that isn't covered by platform APIs. Exposes methods
* to listen to changes in the {@link WindowLayoutInfo}. A {@link WindowLayoutInfo} contains a list
* of {@link DisplayFeature}s.
- *
+ * <p>
* Currently {@link FoldingFeature} is the only {@link DisplayFeature}. A {@link FoldingFeature}
* exposes the state of a hinge and the relative bounds within the window. Developers can
* optimize their UI to support a {@link FoldingFeature} by avoiding it and placing content in
@@ -38,19 +39,70 @@
* @see WindowExtensions#getWindowLayoutComponent()
*/
public interface WindowLayoutComponent {
+ /**
+ * @deprecated Use {@link #addWindowLayoutInfoListener(Context,Consumer)}
+ * starting with {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #addWindowLayoutInfoListener(Context, Consumer)} can't be
+ * called on {@link WindowExtensions#VENDOR_API_LEVEL_1}.
+ */
+ @Deprecated
+ void addWindowLayoutInfoListener(@NonNull Activity activity,
+ @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer);
/**
- * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
- * @param activity hosting a {@link android.view.Window}
- * @param consumer interested in receiving updates to {@link WindowLayoutInfo}
+ * @deprecated Use {@link #removeWindowLayoutInfoListener(Consumer)} starting with
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}. Only used if
+ * {@link #removeWindowLayoutInfoListener(Consumer)} can't be called on
+ * {@link WindowExtensions#VENDOR_API_LEVEL_1}.
*/
- void addWindowLayoutInfoListener(@NonNull Activity activity,
- @NonNull Consumer<WindowLayoutInfo> consumer);
+ @Deprecated
+ void removeWindowLayoutInfoListener(
+ @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer);
+
+ // TODO(b/264546746): Remove addWindowLayoutInfoListener(Context, java.util.function.Consumer)
+ // after apps update to the latest WM Jetpack library.
+ /** @deprecated Use {@link #addWindowLayoutInfoListener(Context, Consumer)} instead */
+ @SuppressWarnings("PairedRegistration")
+ // The paired method for unregistering is also removeWindowLayoutInfoListener.
+ @Deprecated
+ default void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
+ @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
+ throw new UnsupportedOperationException("This method must not be called unless there is a"
+ + " corresponding override implementation on the device.");
+ }
+
+ /**
+ * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}.
+ * Use {@link WindowLayoutComponent#removeWindowLayoutInfoListener} to remove listener.
+ * <p>
+ * A {@link Context} or a Consumer instance can only be registered once.
+ * Registering the same {@link Context} or Consumer more than once will result in
+ * a noop.
+ *
+ * @param context a {@link UiContext} that corresponds to a window or an area on the
+ * screen - an {@link Activity}, a {@link Context} created with
+ * {@link Context#createWindowContext(Display, int , Bundle)}, or
+ * {@link android.inputmethodservice.InputMethodService}.
+ * @param consumer interested in receiving updates to {@link WindowLayoutInfo}
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ // TODO(b/238905747): Add api guard for extensions.
+ @SuppressWarnings("PairedRegistration")
+ // The paired method for unregistering is also removeWindowLayoutInfoListener.
+ default void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
+ @NonNull Consumer<WindowLayoutInfo> consumer) {
+ throw new UnsupportedOperationException("This method must not be called unless there is a"
+ + " corresponding override implementation on the device.");
+ }
/**
* Removes a listener no longer interested in receiving updates.
+ *
* @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
+ * since {@link WindowExtensions#VENDOR_API_LEVEL_2}
*/
- void removeWindowLayoutInfoListener(
- @NonNull Consumer<WindowLayoutInfo> consumer);
+ default void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) {
+ throw new UnsupportedOperationException("This method must not be called unless there is a"
+ + " corresponding override implementation on the device.");
+ }
}
diff --git a/window/extensions/extensions/src/test/java/androidx/window/extensions/embedding/SplitAttributesTest.java b/window/extensions/extensions/src/test/java/androidx/window/extensions/embedding/SplitAttributesTest.java
new file mode 100644
index 0000000..76a1107
--- /dev/null
+++ b/window/extensions/extensions/src/test/java/androidx/window/extensions/embedding/SplitAttributesTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.extensions.embedding;
+
+import static androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType.splitEqually;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.graphics.Color;
+
+import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.SplitAttributes.LayoutDirection;
+
+import org.junit.Test;
+
+/** Test for {@link SplitAttributes} */
+@SmallTest
+public class SplitAttributesTest {
+ @Test
+ public void testSplitAttributesEquals() {
+ final SplitAttributes layout1 = new SplitAttributes.Builder()
+ .setSplitType(splitEqually())
+ .setLayoutDirection(LayoutDirection.LOCALE)
+ .setAnimationBackgroundColor(0)
+ .build();
+ final SplitAttributes layout2 = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(splitEqually()))
+ .setLayoutDirection(LayoutDirection.LOCALE)
+ .setAnimationBackgroundColor(0)
+ .build();
+ final SplitAttributes layout3 = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(splitEqually()))
+ .setLayoutDirection(LayoutDirection.TOP_TO_BOTTOM)
+ .setAnimationBackgroundColor(0)
+ .build();
+ final SplitAttributes layout4 = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(splitEqually()))
+ .setLayoutDirection(LayoutDirection.TOP_TO_BOTTOM)
+ .setAnimationBackgroundColor(Color.BLUE)
+ .build();
+ final SplitAttributes layout5 = new SplitAttributes.Builder()
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(splitEqually()))
+ .setLayoutDirection(LayoutDirection.TOP_TO_BOTTOM)
+ .setAnimationBackgroundColor(Color.BLUE)
+ .build();
+
+ assertNotEquals(layout1, layout2);
+ assertNotEquals(layout1.hashCode(), layout2.hashCode());
+
+ assertNotEquals(layout2, layout3);
+ assertNotEquals(layout2.hashCode(), layout3.hashCode());
+
+ assertNotEquals(layout3, layout1);
+ assertNotEquals(layout3.hashCode(), layout1.hashCode());
+
+ assertNotEquals(layout4, layout3);
+ assertNotEquals(layout4.hashCode(), layout3.hashCode());
+
+ assertEquals(layout4, layout5);
+ assertEquals(layout4.hashCode(), layout5.hashCode());
+ }
+
+ @Test
+ public void testSplitTypeEquals() {
+ final SplitAttributes.SplitType[] splitTypes = new SplitAttributes.SplitType[]{
+ new SplitAttributes.SplitType.ExpandContainersSplitType(),
+ new SplitAttributes.SplitType.RatioSplitType(0.3f),
+ splitEqually(),
+ new SplitAttributes.SplitType.HingeSplitType(splitEqually()),
+ new SplitAttributes.SplitType.HingeSplitType(
+ new SplitAttributes.SplitType.ExpandContainersSplitType()
+ ),
+ };
+
+ for (int i = 0; i < splitTypes.length; i++) {
+ for (int j = 0; j < splitTypes.length; j++) {
+ final SplitAttributes.SplitType splitType0 = splitTypes[i];
+ final SplitAttributes.SplitType splitType1 = splitTypes[j];
+ if (i == j) {
+ assertEquals(splitType0, splitType1);
+ assertEquals(splitType0.hashCode(), splitType1.hashCode());
+ } else {
+ assertNotEquals(splitType0, splitType1);
+ assertNotEquals(splitType0.hashCode(), splitType1.hashCode());
+ }
+ }
+ }
+ }
+}
diff --git a/window/window-samples/build.gradle b/window/window-demos/demo-common/build.gradle
similarity index 61%
copy from window/window-samples/build.gradle
copy to window/window-demos/demo-common/build.gradle
index 99cf856..fd30ac1 100644
--- a/window/window-samples/build.gradle
+++ b/window/window-demos/demo-common/build.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * 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.
@@ -14,28 +14,24 @@
* limitations under the License.
*/
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
plugins {
id("AndroidXPlugin")
- id("com.android.application")
- id("org.jetbrains.kotlin.android")
+ id("com.android.library")
+ id("kotlin-android")
}
android {
defaultConfig {
- applicationId "androidx.window.sample"
minSdkVersion 23
}
buildFeatures {
viewBinding true
}
- namespace "androidx.window.sample"
+ namespace "androidx.window.demo.common"
}
dependencies {
- implementation("androidx.appcompat:appcompat:1.5.1")
+ implementation("androidx.appcompat:appcompat:1.2.0")
implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.activity:activity:1.2.0")
implementation "androidx.recyclerview:recyclerview:1.2.1"
@@ -48,18 +44,4 @@
implementation(project(":window:window-java"))
debugImplementation(libs.leakcanary)
-
- androidTestImplementation(libs.testCore)
- androidTestImplementation(libs.testExtJunit)
- androidTestImplementation(libs.testRunner)
- androidTestImplementation(libs.testRules)
- androidTestImplementation(libs.espressoCore, excludes.espresso)
- androidTestImplementation(project(":window:window-testing"))
-}
-
-androidx {
- name = "Jetpack WindowManager library samples"
- publish = Publish.NONE
- inceptionYear = "2020"
- description = "Demo of Jetpack WindowManager library APIs"
}
diff --git a/window/window-demos/demo-common/src/main/AndroidManifest.xml b/window/window-demos/demo-common/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..134b96c
--- /dev/null
+++ b/window/window-demos/demo-common/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <application
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity
+ android:name=".DisplayFeaturesActivity"
+ android:exported="true"
+ android:supportsPictureInPicture="true"
+ android:configChanges=
+ "orientation|screenSize|screenLayout|screenSize|layoutDirection|smallestScreenSize"
+ android:allowUntrustedActivityEmbedding="true"
+ android:label="@string/display_features_config_change" />
+ </application>
+</manifest>
diff --git a/window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesNoConfigChangeActivity.kt b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/DisplayFeaturesActivity.kt
similarity index 85%
rename from window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesNoConfigChangeActivity.kt
rename to window/window-demos/demo-common/src/main/java/androidx/window/demo/common/DisplayFeaturesActivity.kt
index 729ceb3..1126232 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesNoConfigChangeActivity.kt
+++ b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/DisplayFeaturesActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample
+package androidx.window.demo.common
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
@@ -25,31 +25,33 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import androidx.recyclerview.widget.RecyclerView
+import androidx.window.demo.common.databinding.ActivityDisplayFeaturesConfigChangeBinding
+import androidx.window.demo.common.infolog.InfoLogAdapter
+import androidx.window.demo.common.util.PictureInPictureUtil.appendPictureInPictureMenu
+import androidx.window.demo.common.util.PictureInPictureUtil.handlePictureInPictureMenuItem
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
-import androidx.window.sample.databinding.ActivityDisplayFeaturesNoConfigChangeBinding
-import androidx.window.sample.infolog.InfoLogAdapter
-import androidx.window.sample.util.PictureInPictureUtil.appendPictureInPictureMenu
-import androidx.window.sample.util.PictureInPictureUtil.handlePictureInPictureMenuItem
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
-class DisplayFeaturesNoConfigChangeActivity : AppCompatActivity() {
+/** Demo activity that shows all display features and current device state on the screen. */
+open class DisplayFeaturesActivity : AppCompatActivity() {
private val infoLogAdapter = InfoLogAdapter()
private val displayFeatureViews = ArrayList<View>()
- private lateinit var binding: ActivityDisplayFeaturesNoConfigChangeBinding
+ private lateinit var binding: ActivityDisplayFeaturesConfigChangeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = ActivityDisplayFeaturesNoConfigChangeBinding.inflate(layoutInflater)
+ binding = ActivityDisplayFeaturesConfigChangeBinding.inflate(layoutInflater)
setContentView(binding.root)
- val recyclerView = binding.infoLogRecyclerView
+ val recyclerView = findViewById<RecyclerView>(R.id.infoLogRecyclerView)
recyclerView.adapter = infoLogAdapter
lifecycleScope.launch(Dispatchers.Main) {
@@ -59,8 +61,8 @@
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Safely collect from windowInfoRepo when the lifecycle is STARTED
// and stops collection when the lifecycle is STOPPED
- WindowInfoTracker.getOrCreate(this@DisplayFeaturesNoConfigChangeActivity)
- .windowLayoutInfo(this@DisplayFeaturesNoConfigChangeActivity)
+ WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
+ .windowLayoutInfo(this@DisplayFeaturesActivity)
.collect { newLayoutInfo ->
// New posture information
updateStateLog(newLayoutInfo)
diff --git a/window/window-samples/src/main/java/androidx/window/sample/SampleTools.kt b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/SampleTools.kt
similarity index 95%
rename from window/window-samples/src/main/java/androidx/window/sample/SampleTools.kt
rename to window/window-demos/demo-common/src/main/java/androidx/window/demo/common/SampleTools.kt
index b7c3a4d..ff88b99 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/SampleTools.kt
+++ b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/SampleTools.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * 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.
@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.window.sample
+package androidx.window.demo.common
+
import android.graphics.Rect
import android.view.View
import android.widget.FrameLayout
diff --git a/window/window-samples/src/main/java/androidx/window/sample/infolog/InfoLog.kt b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/infolog/InfoLog.kt
similarity index 89%
rename from window/window-samples/src/main/java/androidx/window/sample/infolog/InfoLog.kt
rename to window/window-demos/demo-common/src/main/java/androidx/window/demo/common/infolog/InfoLog.kt
index e69a77b..3ab4096 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/infolog/InfoLog.kt
+++ b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/infolog/InfoLog.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample.infolog
+package androidx.window.demo.common.infolog
/**
* A data class to hold a title and a detail or subtitle that can be shown using [InfoLogAdapter]
diff --git a/window/window-samples/src/main/java/androidx/window/sample/infolog/InfoLogAdapter.kt b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/infolog/InfoLogAdapter.kt
similarity index 82%
rename from window/window-samples/src/main/java/androidx/window/sample/infolog/InfoLogAdapter.kt
rename to window/window-demos/demo-common/src/main/java/androidx/window/demo/common/infolog/InfoLogAdapter.kt
index 29fefd6..81df057 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/infolog/InfoLogAdapter.kt
+++ b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/infolog/InfoLogAdapter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.window.sample.infolog
+package androidx.window.demo.common.infolog
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import androidx.window.sample.R
+import androidx.window.demo.common.R
class InfoLogAdapter : RecyclerView.Adapter<InfoLogVH>() {
@@ -34,14 +34,19 @@
override fun onBindViewHolder(holder: InfoLogVH, position: Int) {
val item = items[position]
- holder.titleView.text = "ID: ${item.id} Title: ${item.title}"
- holder.detailView.text = "Detail: ${item.detail}"
+ holder.titleView.text = "[ID${item.id}] ${item.title}"
+ holder.detailView.text = item.detail
}
override fun getItemCount(): Int {
return items.size
}
+ fun clear() {
+ items.clear()
+ id = 0
+ }
+
fun append(title: String, message: String) {
append(InfoLog(title, message, id))
++id
diff --git a/window/window-samples/src/main/java/androidx/window/sample/infolog/InfoLogVH.kt b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/infolog/InfoLogVH.kt
similarity index 86%
rename from window/window-samples/src/main/java/androidx/window/sample/infolog/InfoLogVH.kt
rename to window/window-demos/demo-common/src/main/java/androidx/window/demo/common/infolog/InfoLogVH.kt
index 4760be2..1bf7dfe 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/infolog/InfoLogVH.kt
+++ b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/infolog/InfoLogVH.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.window.sample.infolog
+package androidx.window.demo.common.infolog
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
-import androidx.window.sample.R
+import androidx.window.demo.common.R
class InfoLogVH(view: View) : RecyclerView.ViewHolder(view) {
val titleView: TextView = view.findViewById(R.id.title_view)
diff --git a/window/window-samples/src/main/java/androidx/window/sample/util/PictureInPictureUtil.kt b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/util/PictureInPictureUtil.kt
similarity index 96%
rename from window/window-samples/src/main/java/androidx/window/sample/util/PictureInPictureUtil.kt
rename to window/window-demos/demo-common/src/main/java/androidx/window/demo/common/util/PictureInPictureUtil.kt
index 4f50715..70eedab 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/util/PictureInPictureUtil.kt
+++ b/window/window-demos/demo-common/src/main/java/androidx/window/demo/common/util/PictureInPictureUtil.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample.util
+package androidx.window.demo.common.util
import android.app.Activity
import android.app.PictureInPictureParams
@@ -24,7 +24,7 @@
import android.view.MenuItem
import android.widget.Toast
import androidx.annotation.RequiresApi
-import androidx.window.sample.R
+import androidx.window.demo.common.R
@RequiresApi(Build.VERSION_CODES.O)
private object PictureInPictureLauncherO {
diff --git a/window/window-samples/src/main/res/layout/activity_display_features_no_config_change.xml b/window/window-demos/demo-common/src/main/res/layout/activity_display_features_config_change.xml
similarity index 92%
rename from window/window-samples/src/main/res/layout/activity_display_features_no_config_change.xml
rename to window/window-demos/demo-common/src/main/res/layout/activity_display_features_config_change.xml
index 46ade36..419ab11 100644
--- a/window/window-samples/src/main/res/layout/activity_display_features_no_config_change.xml
+++ b/window/window-demos/demo-common/src/main/res/layout/activity_display_features_config_change.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2021 The Android Open Source Project
+ 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.
@@ -22,7 +22,7 @@
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context="androidx.window.sample.DisplayFeaturesNoConfigChangeActivity">
+ tools:context="androidx.window.demo.common.DisplayFeaturesActivity">
<FrameLayout
android:id="@+id/feature_container_layout"
@@ -38,7 +38,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/current_state"
- android:textAppearance="@style/TextAppearance.AppCompat.Large"
+ android:textAppearance="@style/TextAppearance.AppCompat"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
@@ -87,7 +87,7 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/current_state"
+ app:layout_constraintTop_toBottomOf="@id/current_state"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/layout/view_holder_info_log.xml b/window/window-demos/demo-common/src/main/res/layout/view_holder_info_log.xml
similarity index 93%
rename from window/window-samples/src/main/res/layout/view_holder_info_log.xml
rename to window/window-demos/demo-common/src/main/res/layout/view_holder_info_log.xml
index 7487737..e7e20c1 100644
--- a/window/window-samples/src/main/res/layout/view_holder_info_log.xml
+++ b/window/window-demos/demo-common/src/main/res/layout/view_holder_info_log.xml
@@ -24,7 +24,8 @@
<TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:textStyle="bold"/>
<TextView
android:id="@+id/detail_view"
diff --git a/window/window-samples/src/main/res/menu/picture_in_picture_menu.xml b/window/window-demos/demo-common/src/main/res/menu/picture_in_picture_menu.xml
similarity index 100%
copy from window/window-samples/src/main/res/menu/picture_in_picture_menu.xml
copy to window/window-demos/demo-common/src/main/res/menu/picture_in_picture_menu.xml
diff --git a/window/window-samples/src/main/res/values/colors.xml b/window/window-demos/demo-common/src/main/res/values/colors.xml
similarity index 85%
copy from window/window-samples/src/main/res/values/colors.xml
copy to window/window-demos/demo-common/src/main/res/values/colors.xml
index 41a72b2..be90b3a 100644
--- a/window/window-samples/src/main/res/values/colors.xml
+++ b/window/window-demos/demo-common/src/main/res/values/colors.xml
@@ -16,9 +16,9 @@
-->
<resources>
- <color name="colorPrimary">#6200EE</color>
- <color name="colorPrimaryDark">#3700B3</color>
- <color name="colorAccent">#03DAC5</color>
+ <color name="colorPrimary">#03A9F4</color>
+ <color name="colorPrimaryDark">#354395</color>
+ <color name="colorAccent">#009688</color>
<color name="colorFeatureFold">#7700FF00</color>
diff --git a/window/window-demos/demo-common/src/main/res/values/strings.xml b/window/window-demos/demo-common/src/main/res/values/strings.xml
new file mode 100644
index 0000000..aeb9f83
--- /dev/null
+++ b/window/window-demos/demo-common/src/main/res/values/strings.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<resources>
+ <string name="trusted_embedding_activity">Trusted Embedding Activity</string>
+ <string name="trusted_embedding_activity_detail">Activity allows embedding in trusted mode via a
+ known certificate.</string>
+ <string name="untrusted_embedding_activity">Untrusted Embedding Activity</string>
+ <string name="untrusted_embedding_activity_detail">Activity allows embedding in untrusted mode
+ via opt-in.</string>
+ <string name="display_features_config_change">Display features handle config change</string>
+ <string name="fold">Fold</string>
+ <string name="legend">Legend:</string>
+ <string name="current_state">Current state</string>
+ <string name="window_layout">Window layout</string>
+ <string name="screens_are_separated">"Screens are separated"</string>
+ <string name="screens_are_not_separated">"Screen is not separated"</string>
+ <string name="screen_is_horizontal">"Hinge is horizontal"</string>
+ <string name="screen_is_vertical">"Hinge is vertical"</string>
+ <string name="occlusion_is_full">Full occlusion</string>
+ <string name="occlusion_is_none">No occlusion</string>
+</resources>
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/values/styles.xml b/window/window-demos/demo-common/src/main/res/values/styles.xml
similarity index 94%
rename from window/window-samples/src/main/res/values/styles.xml
rename to window/window-demos/demo-common/src/main/res/values/styles.xml
index eaa9ab2..6f9733b 100644
--- a/window/window-samples/src/main/res/values/styles.xml
+++ b/window/window-demos/demo-common/src/main/res/values/styles.xml
@@ -1,5 +1,5 @@
<!--
- Copyright 2020 The Android Open Source Project
+ 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.
diff --git a/window/window-demos/demo-second-app/build.gradle b/window/window-demos/demo-second-app/build.gradle
new file mode 100644
index 0000000..6af138b
--- /dev/null
+++ b/window/window-demos/demo-second-app/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ defaultConfig {
+ applicationId "androidx.window.demo2"
+ minSdkVersion 23
+ }
+ buildFeatures {
+ viewBinding true
+ }
+ namespace "androidx.window.demo2"
+}
+
+dependencies {
+ implementation("androidx.activity:activity:1.2.0")
+ implementation("androidx.appcompat:appcompat:1.2.0")
+ api(libs.constraintLayout)
+ implementation("androidx.core:core-ktx:1.8.0")
+ // TODO(b/152245564) Conflicting dependencies cause IDE errors.
+ implementation("androidx.lifecycle:lifecycle-viewmodel:2.4.0-alpha02")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha02")
+ implementation("androidx.recyclerview:recyclerview:1.2.1")
+ implementation(project(":window:window-java"))
+ implementation(project(":window:window-demos:demo-common"))
+}
diff --git a/window/window-demos/demo-second-app/src/main/AndroidManifest.xml b/window/window-demos/demo-second-app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4d9f6c1
--- /dev/null
+++ b/window/window-demos/demo-second-app/src/main/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <application
+ android:label="Activity Embedding Sample"
+ android:supportsRtl="true">
+ <activity
+ android:name=".embedding.TrustedEmbeddingActivity"
+ android:exported="true"
+ android:label="@string/trusted_embedding_activity"
+ android:configChanges=
+ "orientation|screenSize|screenLayout|screenSize|layoutDirection|smallestScreenSize"
+ android:knownActivityEmbeddingCerts=
+ "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".embedding.UntrustedEmbeddingActivity"
+ android:exported="true"
+ android:label="@string/untrusted_embedding_activity"
+ android:configChanges=
+ "orientation|screenSize|screenLayout|screenSize|layoutDirection|smallestScreenSize"
+ android:allowUntrustedActivityEmbedding="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity-alias
+ android:name="androidx.window.demo2.DisplayFeaturesActivity"
+ android:targetActivity="androidx.window.demo.common.DisplayFeaturesActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+ </application>
+</manifest>
diff --git a/window/window-demos/demo-second-app/src/main/java/androidx/window/demo2/embedding/TrustedEmbeddingActivity.kt b/window/window-demos/demo-second-app/src/main/java/androidx/window/demo2/embedding/TrustedEmbeddingActivity.kt
new file mode 100644
index 0000000..bc2e7cd
--- /dev/null
+++ b/window/window-demos/demo-second-app/src/main/java/androidx/window/demo2/embedding/TrustedEmbeddingActivity.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.demo2.embedding
+
+import android.app.Activity
+import android.os.Bundle
+import android.widget.TextView
+import androidx.window.demo2.R
+
+/**
+ * Activity that can be embedded by a process with a known certificate. See
+ * `android:allowUntrustedActivityEmbedding` in AndroidManifest. Activity can be launched from the
+ * split demos in window-samples/demos.
+ */
+class TrustedEmbeddingActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_embedded)
+ findViewById<TextView>(R.id.detail_text_view).text =
+ getString(R.string.trusted_embedding_activity_detail)
+ }
+}
\ No newline at end of file
diff --git a/window/window-demos/demo-second-app/src/main/java/androidx/window/demo2/embedding/UntrustedEmbeddingActivity.kt b/window/window-demos/demo-second-app/src/main/java/androidx/window/demo2/embedding/UntrustedEmbeddingActivity.kt
new file mode 100644
index 0000000..f26aeee
--- /dev/null
+++ b/window/window-demos/demo-second-app/src/main/java/androidx/window/demo2/embedding/UntrustedEmbeddingActivity.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.demo2.embedding
+
+import android.app.Activity
+import android.os.Bundle
+import android.widget.TextView
+import androidx.window.demo2.R
+
+/**
+ * Activity that can be embedded in untrusted mode. See
+ * `android:allowUntrustedActivityEmbedding` in AndroidManifest. Activity can be launched from
+ * the split demos in window-samples/demos.
+ */
+class UntrustedEmbeddingActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_embedded)
+ findViewById<TextView>(R.id.detail_text_view).text =
+ getString(R.string.untrusted_embedding_activity_detail)
+ }
+}
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/layout/test_ime.xml b/window/window-demos/demo-second-app/src/main/res/layout/activity_embedded.xml
similarity index 61%
rename from window/window-samples/src/main/res/layout/test_ime.xml
rename to window/window-demos/demo-second-app/src/main/res/layout/activity_embedded.xml
index feda1d7..fbd572c 100644
--- a/window/window-samples/src/main/res/layout/test_ime.xml
+++ b/window/window-demos/demo-second-app/src/main/res/layout/activity_embedded.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,15 +15,14 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="match_parent">
- <Button
- android:id="@+id/button_close"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/test_ime_button_close"/>
+ <TextView
+ android:layout_gravity="center"
+ android:id="@+id/detail_text_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
-</LinearLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/window/window-demos/demo-second-app/src/main/res/values/strings.xml b/window/window-demos/demo-second-app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..de4172b
--- /dev/null
+++ b/window/window-demos/demo-second-app/src/main/res/values/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<resources>
+ <string name="trusted_embedding_activity">Trusted Embedding Activity</string>
+ <string name="trusted_embedding_activity_detail">Activity allows embedding in trusted mode via a
+ known certificate.</string>
+ <string name="untrusted_embedding_activity">Untrusted Embedding Activity</string>
+ <string name="untrusted_embedding_activity_detail">Activity allows embedding in untrusted mode
+ via opt-in.</string>
+</resources>
\ No newline at end of file
diff --git a/window/window-demos/demo/README.md b/window/window-demos/demo/README.md
new file mode 100644
index 0000000..f5f7378
--- /dev/null
+++ b/window/window-demos/demo/README.md
@@ -0,0 +1,6 @@
+# WindowManager Jetpack Demos
+
+The `keystore.jks` was generated using sample keys and certificates from Android AOSP. It is used to
+sign the demo app with a known key and showcase the usage of ActivityEmbedding APIs across apps
+for known certificates. See `build.gradle` for signing and
+`window-samples/demo-second-app` for the usage of the known certificate digest.
diff --git a/window/window-samples/build.gradle b/window/window-demos/demo/build.gradle
similarity index 67%
rename from window/window-samples/build.gradle
rename to window/window-demos/demo/build.gradle
index 99cf856..e579b7f 100644
--- a/window/window-samples/build.gradle
+++ b/window/window-demos/demo/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import androidx.build.Publish
+import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
@@ -25,13 +25,29 @@
android {
defaultConfig {
- applicationId "androidx.window.sample"
+ applicationId "androidx.window.demo"
minSdkVersion 23
}
buildFeatures {
viewBinding true
}
- namespace "androidx.window.sample"
+ namespace "androidx.window.demo"
+ signingConfigs {
+ config {
+ keyAlias 'alias'
+ keyPassword 'password'
+ storeFile file('keystore.jks')
+ storePassword 'password'
+ }
+ }
+ buildTypes {
+ release {
+ signingConfig signingConfigs.config
+ }
+ debug {
+ signingConfig signingConfigs.config
+ }
+ }
}
dependencies {
@@ -47,6 +63,7 @@
implementation("androidx.startup:startup-runtime:1.1.0")
implementation(project(":window:window-java"))
+ implementation(project(":window:window-demos:demo-common"))
debugImplementation(libs.leakcanary)
androidTestImplementation(libs.testCore)
@@ -55,11 +72,19 @@
androidTestImplementation(libs.testRules)
androidTestImplementation(libs.espressoCore, excludes.espresso)
androidTestImplementation(project(":window:window-testing"))
+ androidTestImplementation(project(":window:window-demos:demo-common"))
+}
+
+// Allow usage of Kotlin's @OptIn.
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn"]
+ }
}
androidx {
- name = "Jetpack WindowManager library samples"
- publish = Publish.NONE
- inceptionYear = "2020"
- description = "Demo of Jetpack WindowManager library APIs"
-}
+ name = "WM Jetpack Samples"
+ type = LibraryType.SAMPLES
+ inceptionYear = "2023"
+ description = "Samples for the WM Jetpack Library"
+}
\ No newline at end of file
diff --git a/window/window-demos/demo/keystore.jks b/window/window-demos/demo/keystore.jks
new file mode 100644
index 0000000..35e18c9
--- /dev/null
+++ b/window/window-demos/demo/keystore.jks
Binary files differ
diff --git a/window/window-samples/src/androidTest/java/androidx/window/sample/DisplayFeaturesNoConfigChangeActivityTest.kt b/window/window-demos/demo/src/androidTest/java/androidx/window/demo/DisplayFeaturesNoConfigChangeActivityTest.kt
similarity index 98%
rename from window/window-samples/src/androidTest/java/androidx/window/sample/DisplayFeaturesNoConfigChangeActivityTest.kt
rename to window/window-demos/demo/src/androidTest/java/androidx/window/demo/DisplayFeaturesNoConfigChangeActivityTest.kt
index d0ff11b..53f1c78 100644
--- a/window/window-samples/src/androidTest/java/androidx/window/sample/DisplayFeaturesNoConfigChangeActivityTest.kt
+++ b/window/window-demos/demo/src/androidTest/java/androidx/window/demo/DisplayFeaturesNoConfigChangeActivityTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample
+package androidx.window.demo
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
diff --git a/window/window-samples/src/androidTest/java/androidx/window/sample/SplitLayoutActivityTest.kt b/window/window-demos/demo/src/androidTest/java/androidx/window/demo/SplitLayoutActivityTest.kt
similarity index 98%
rename from window/window-samples/src/androidTest/java/androidx/window/sample/SplitLayoutActivityTest.kt
rename to window/window-demos/demo/src/androidTest/java/androidx/window/demo/SplitLayoutActivityTest.kt
index f15e2ed..d3aa678d 100644
--- a/window/window-samples/src/androidTest/java/androidx/window/sample/SplitLayoutActivityTest.kt
+++ b/window/window-demos/demo/src/androidTest/java/androidx/window/demo/SplitLayoutActivityTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample
+package androidx.window.demo
import android.graphics.Rect
import android.view.View
@@ -32,6 +32,7 @@
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import androidx.window.demo.common.adjustFeaturePositionOffset
import androidx.window.layout.FoldingFeature
import androidx.window.layout.FoldingFeature.Orientation.Companion.HORIZONTAL
import androidx.window.layout.FoldingFeature.Orientation.Companion.VERTICAL
diff --git a/window/window-samples/src/main/AndroidManifest.xml b/window/window-demos/demo/src/main/AndroidManifest.xml
similarity index 74%
rename from window/window-samples/src/main/AndroidManifest.xml
rename to window/window-demos/demo/src/main/AndroidManifest.xml
index 17431f8..36b547be 100644
--- a/window/window-samples/src/main/AndroidManifest.xml
+++ b/window/window-demos/demo/src/main/AndroidManifest.xml
@@ -19,7 +19,11 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
- <service android:name="androidx.window.sample.TestIme"
+ <property
+ android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
+ android:value="true" />
+
+ <service android:name=".TestIme"
android:label="@string/test_ime"
android:permission="android.permission.BIND_INPUT_METHOD"
android:exported="true">
@@ -42,13 +46,6 @@
android:exported="false"
android:label="@string/presentation" />
<activity
- android:name=".DisplayFeaturesConfigChangeActivity"
- android:exported="false"
- android:supportsPictureInPicture="true"
- android:configChanges=
- "orientation|screenSize|screenLayout|screenSize|layoutDirection|smallestScreenSize"
- android:label="@string/display_features_config_change" />
- <activity
android:name=".DisplayFeaturesNoConfigChangeActivity"
android:exported="false"
android:supportsPictureInPicture="true"
@@ -66,7 +63,7 @@
android:exported="true"
android:label="Split Main"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.manual_split_affinity">
+ android:taskAffinity="androidx.window.demo.manual_split_affinity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -77,43 +74,50 @@
android:exported="false"
android:label="B"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.manual_split_affinity"/>
+ android:taskAffinity="androidx.window.demo.manual_split_affinity"/>
<activity
android:name=".embedding.SplitActivityC"
android:exported="false"
android:label="C"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.manual_split_affinity"/>
+ android:taskAffinity="androidx.window.demo.manual_split_affinity"/>
<activity
android:name=".embedding.SplitActivityD"
android:exported="false"
android:label="D"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.manual_split_affinity"/>
+ android:taskAffinity="androidx.window.demo.manual_split_affinity"/>
<activity
android:name=".embedding.SplitActivityE"
android:exported="false"
android:label="E"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.manual_split_affinity"/>
+ android:taskAffinity="androidx.window.demo.manual_split_affinity"/>
<activity
android:name=".embedding.SplitActivityF"
android:exported="false"
android:label="F"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.manual_split_affinity"/>
+ android:taskAffinity="androidx.window.demo.manual_split_affinity"/>
<activity
android:name=".embedding.SplitActivityPlaceholder"
android:exported="false"
android:label="Placeholder"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.manual_split_affinity"/>
+ android:taskAffinity="androidx.window.demo.manual_split_affinity"/>
+ <activity
+ android:name=".embedding.ExpandedDialogActivity"
+ android:theme="@style/ExpandedDialogTheme"
+ android:exported="false"
+ android:label="Dialog Activity"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
+ android:taskAffinity="androidx.window.demo.manual_split_affinity"/>
<activity
android:name=".embedding.SplitActivityTrampoline"
android:exported="true"
android:label="Split Trampoline"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.trampoline_affinity">
+ android:taskAffinity="androidx.window.demo.trampoline_affinity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -127,7 +131,7 @@
android:exported="true"
android:label="Split List"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.list_detail_split_affinity">
+ android:taskAffinity="androidx.window.demo.list_detail_split_affinity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -139,13 +143,13 @@
android:label="Item detail"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
android:launchMode="singleTop"
- android:taskAffinity="androidx.window.sample.list_detail_split_affinity"/>
+ android:taskAffinity="androidx.window.demo.list_detail_split_affinity"/>
<activity
android:name=".embedding.SplitActivityListPlaceholder"
android:exported="false"
android:label="Placeholder"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.list_detail_split_affinity" />
+ android:taskAffinity="androidx.window.demo.list_detail_split_affinity" />
<!-- Split PiP App -->
@@ -155,7 +159,7 @@
android:label="Split and PiP"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
android:supportsPictureInPicture="true"
- android:taskAffinity="androidx.window.sample.split_pip">
+ android:taskAffinity="androidx.window.demo.split_pip">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -167,21 +171,43 @@
android:label="PiP B"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
android:supportsPictureInPicture="true"
- android:taskAffinity="androidx.window.sample.split_pip">
+ android:taskAffinity="androidx.window.demo.split_pip">
</activity>
<activity
android:name=".embedding.SplitPipActivityNoPip"
android:exported="false"
android:label="No PiP support"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.split_pip">
+ android:taskAffinity="androidx.window.demo.split_pip">
</activity>
<activity
android:name=".embedding.SplitPipActivityPlaceholder"
android:exported="false"
android:label="PiP Placeholder"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
- android:taskAffinity="androidx.window.sample.split_pip">
+ android:taskAffinity="androidx.window.demo.split_pip">
+ </activity>
+
+ <!-- The demo App to show how to change the current split layout with the current device and
+ window states -->
+
+ <activity
+ android:name=".embedding.SplitDeviceStateActivityA"
+ android:exported="true"
+ android:label="Split on Device State"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
+ android:taskAffinity="androidx.window.demo.split_device_state_activity_affinity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".embedding.SplitDeviceStateActivityB"
+ android:exported="true"
+ android:label="Split on Device State B"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
+ android:taskAffinity="androidx.window.demo.split_device_state_activity_affinity">
</activity>
<!-- The demo app that shows various IME-related use cases -->
@@ -198,7 +224,7 @@
android:exported="false"
tools:node="merge">
<!-- This entry makes ExampleWindowInitializer discoverable. -->
- <meta-data android:name="androidx.window.sample.embedding.ExampleWindowInitializer"
+ <meta-data android:name="androidx.window.demo.embedding.ExampleWindowInitializer"
android:value="androidx.startup" />
</provider>
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityListPlaceholder.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/DisplayFeaturesNoConfigChangeActivity.kt
similarity index 78%
copy from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityListPlaceholder.kt
copy to window/window-demos/demo/src/main/java/androidx/window/demo/DisplayFeaturesNoConfigChangeActivity.kt
index 754e11d8..bd77218 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityListPlaceholder.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/DisplayFeaturesNoConfigChangeActivity.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo
-open class SplitActivityListPlaceholder : SplitActivityPlaceholder()
\ No newline at end of file
+import androidx.window.demo.common.DisplayFeaturesActivity
+
+class DisplayFeaturesNoConfigChangeActivity : DisplayFeaturesActivity()
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/ImeActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/ImeActivity.kt
similarity index 97%
rename from window/window-samples/src/main/java/androidx/window/sample/ImeActivity.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/ImeActivity.kt
index 5ee6536..a31144d 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/ImeActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/ImeActivity.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample
+package androidx.window.demo
import android.content.Intent
import android.os.Bundle
diff --git a/window/window-samples/src/main/java/androidx/window/sample/PresentationActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/PresentationActivity.kt
similarity index 99%
rename from window/window-samples/src/main/java/androidx/window/sample/PresentationActivity.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/PresentationActivity.kt
index d9001c8..5b4cb9c 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/PresentationActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/PresentationActivity.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample
+package androidx.window.demo
import android.app.Presentation
import android.content.Context
diff --git a/window/window-samples/src/main/java/androidx/window/sample/SplitLayout.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/SplitLayout.kt
similarity index 98%
rename from window/window-samples/src/main/java/androidx/window/sample/SplitLayout.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/SplitLayout.kt
index ca17b23..06ba0be 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/SplitLayout.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/SplitLayout.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample
+package androidx.window.demo
import android.content.Context
import android.graphics.Rect
@@ -23,6 +23,7 @@
import android.view.View.MeasureSpec.AT_MOST
import android.view.View.MeasureSpec.EXACTLY
import android.widget.FrameLayout
+import androidx.window.demo.common.adjustFeaturePositionOffset
import androidx.window.layout.DisplayFeature
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowLayoutInfo
diff --git a/window/window-samples/src/main/java/androidx/window/sample/SplitLayoutActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/SplitLayoutActivity.kt
similarity index 98%
rename from window/window-samples/src/main/java/androidx/window/sample/SplitLayoutActivity.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/SplitLayoutActivity.kt
index 0bcdc5d..89290a9e 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/SplitLayoutActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/SplitLayoutActivity.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample
+package androidx.window.demo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/TestIme.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/TestIme.kt
new file mode 100644
index 0000000..a63b171
--- /dev/null
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/TestIme.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.demo
+
+import android.inputmethodservice.InputMethodService
+import android.os.Build
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import android.widget.Button
+import androidx.core.view.WindowInsetsCompat.Type
+import androidx.recyclerview.widget.RecyclerView
+import androidx.window.core.ExperimentalWindowApi
+import androidx.window.demo.common.infolog.InfoLogAdapter
+import androidx.window.layout.WindowMetrics
+import androidx.window.layout.WindowMetricsCalculator
+
+/**
+ * A test IME that currently provides a minimal UI containing a "Close" button. To use this, go to
+ * "Settings > System > Languages & Input > On-screen keyboard" and enable "Test IME". Remember you
+ * may still need to switch to this IME after the default on-screen keyboard pops up.
+ */
+internal class TestIme : InputMethodService() {
+
+ private val adapter = InfoLogAdapter()
+
+ override fun onCreateInputView(): View {
+ return layoutInflater.inflate(R.layout.test_ime, null).apply {
+ findViewById<RecyclerView>(R.id.recycler_view).adapter = adapter
+
+ findViewById<Button>(R.id.button_clear).setOnClickListener {
+ adapter.clear()
+ adapter.notifyDataSetChanged()
+ }
+
+ findViewById<Button>(R.id.button_close).setOnClickListener {
+ requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS)
+ }
+
+ displayCurrentWindowMetrics()
+ displayMaximumWindowMetrics()
+ }
+ }
+
+ private fun displayCurrentWindowMetrics() {
+ val windowMetrics = WindowMetricsCalculator.getOrCreate()
+ .computeCurrentWindowMetrics(this@TestIme)
+ displayWindowMetrics("CurrentWindowMetrics update", windowMetrics)
+ }
+
+ private fun displayMaximumWindowMetrics() {
+ val windowMetrics = WindowMetricsCalculator.getOrCreate()
+ .computeMaximumWindowMetrics(this@TestIme)
+ displayWindowMetrics("MaximumWindowMetrics update", windowMetrics)
+ }
+
+ @OptIn(ExperimentalWindowApi::class)
+ private fun displayWindowMetrics(title: String, windowMetrics: WindowMetrics) {
+
+ val width = windowMetrics.bounds.width()
+ val height = windowMetrics.bounds.height()
+
+ val logBuilder = StringBuilder().append("Width: $width, Height: $height\n" +
+ "Top: ${windowMetrics.bounds.top}, Bottom: ${windowMetrics.bounds.bottom}, " +
+ "Left: ${windowMetrics.bounds.left}, Right: ${windowMetrics.bounds.right}")
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val windowInsets = windowMetrics.getWindowInsets()
+ logBuilder.append("\nimeInset: ${windowInsets.getInsets(Type.ime())}")
+ logBuilder.append("\nnavInset: ${windowInsets.getInsets(Type.navigationBars())}")
+ logBuilder.append("\nstatusBarInset: ${windowInsets.getInsets(Type.statusBars())}")
+ }
+ adapter.append(title, logBuilder.toString())
+ adapter.notifyDataSetChanged()
+ }
+
+ override fun onEvaluateFullscreenMode(): Boolean {
+ return false
+ }
+}
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/WindowMetricsActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/WindowMetricsActivity.kt
similarity index 94%
rename from window/window-samples/src/main/java/androidx/window/sample/WindowMetricsActivity.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/WindowMetricsActivity.kt
index 525648b..4b0faa7 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/WindowMetricsActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/WindowMetricsActivity.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package androidx.window.sample
+package androidx.window.demo
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.window.layout.WindowMetricsCalculator
-import androidx.window.sample.infolog.InfoLogAdapter
+import androidx.window.demo.common.infolog.InfoLogAdapter
class WindowMetricsActivity : AppCompatActivity() {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/demos/DemoAdapter.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/demos/DemoAdapter.kt
similarity index 94%
rename from window/window-samples/src/main/java/androidx/window/sample/demos/DemoAdapter.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/demos/DemoAdapter.kt
index 8cb8aca..5bc46ae 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/demos/DemoAdapter.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/demos/DemoAdapter.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.window.sample.demos
+package androidx.window.demo.demos
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import androidx.window.sample.R
+import androidx.window.demo.R
class DemoAdapter(private val demoItems: List<DemoItem>) : RecyclerView.Adapter<DemoVH>() {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/demos/DemoItem.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/demos/DemoItem.kt
similarity index 94%
rename from window/window-samples/src/main/java/androidx/window/sample/demos/DemoItem.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/demos/DemoItem.kt
index fb08562..88f3c65 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/demos/DemoItem.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/demos/DemoItem.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.window.sample.demos
+package androidx.window.demo.demos
import android.app.Activity
diff --git a/window/window-samples/src/main/java/androidx/window/sample/demos/DemoVH.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/demos/DemoVH.kt
similarity index 94%
rename from window/window-samples/src/main/java/androidx/window/sample/demos/DemoVH.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/demos/DemoVH.kt
index 5c08cc1..d986288 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/demos/DemoVH.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/demos/DemoVH.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package androidx.window.sample.demos
+package androidx.window.demo.demos
import android.content.Intent
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
-import androidx.window.sample.R
+import androidx.window.demo.R
class DemoVH(view: View) : RecyclerView.ViewHolder(view) {
private val description = view.findViewById<TextView>(R.id.demo_description)
diff --git a/window/window-samples/src/main/java/androidx/window/sample/demos/WindowDemosActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/demos/WindowDemosActivity.kt
similarity index 76%
rename from window/window-samples/src/main/java/androidx/window/sample/demos/WindowDemosActivity.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/demos/WindowDemosActivity.kt
index dd2f206..19c0156 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/demos/WindowDemosActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/demos/WindowDemosActivity.kt
@@ -14,22 +14,22 @@
* limitations under the License.
*/
-package androidx.window.sample.demos
+package androidx.window.demo.demos
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
-import androidx.window.sample.DisplayFeaturesConfigChangeActivity
-import androidx.window.sample.DisplayFeaturesNoConfigChangeActivity
-import androidx.window.sample.ImeActivity
-import androidx.window.sample.PresentationActivity
-import androidx.window.sample.R
-import androidx.window.sample.R.string.display_features_config_change
-import androidx.window.sample.R.string.display_features_no_config_change
-import androidx.window.sample.R.string.show_all_display_features_config_change_description
-import androidx.window.sample.R.string.show_all_display_features_no_config_change_description
-import androidx.window.sample.SplitLayoutActivity
-import androidx.window.sample.WindowMetricsActivity
+import androidx.window.demo.DisplayFeaturesNoConfigChangeActivity
+import androidx.window.demo.ImeActivity
+import androidx.window.demo.PresentationActivity
+import androidx.window.demo.R
+import androidx.window.demo.R.string.display_features_config_change
+import androidx.window.demo.R.string.display_features_no_config_change
+import androidx.window.demo.R.string.show_all_display_features_config_change_description
+import androidx.window.demo.R.string.show_all_display_features_no_config_change_description
+import androidx.window.demo.SplitLayoutActivity
+import androidx.window.demo.WindowMetricsActivity
+import androidx.window.demo.common.DisplayFeaturesActivity
/**
* Main activity that launches WindowManager demos.
@@ -43,7 +43,7 @@
DemoItem(
buttonTitle = getString(display_features_config_change),
description = getString(show_all_display_features_config_change_description),
- clazz = DisplayFeaturesConfigChangeActivity::class.java
+ clazz = DisplayFeaturesActivity::class.java
),
DemoItem(
buttonTitle = getString(display_features_no_config_change),
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/DemoActivityEmbeddingController.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/DemoActivityEmbeddingController.kt
new file mode 100644
index 0000000..0d3771e
--- /dev/null
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/DemoActivityEmbeddingController.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.demo.embedding
+
+import androidx.annotation.ColorInt
+import androidx.annotation.GuardedBy
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+/** A singleton controller to manage the global config. */
+class DemoActivityEmbeddingController private constructor() {
+
+ private val lock = Object()
+
+ @GuardedBy("lock")
+ @ColorInt
+ private var _animationBackgroundColor = 0
+
+ /** Animation background color to use when the animation requires a background. */
+ var animationBackgroundColor: Int
+ @ColorInt
+ get() = synchronized(lock) {
+ _animationBackgroundColor
+ }
+ set(@ColorInt value) = synchronized(lock) {
+ _animationBackgroundColor = value
+ }
+
+ companion object {
+ @Volatile
+ private var globalInstance: DemoActivityEmbeddingController? = null
+ private val globalLock = ReentrantLock()
+
+ /**
+ * Obtains the singleton instance of [DemoActivityEmbeddingController].
+ */
+ @JvmStatic
+ fun getInstance(): DemoActivityEmbeddingController {
+ if (globalInstance == null) {
+ globalLock.withLock {
+ if (globalInstance == null) {
+ globalInstance = DemoActivityEmbeddingController()
+ }
+ }
+ }
+ return globalInstance!!
+ }
+ }
+}
\ No newline at end of file
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
new file mode 100644
index 0000000..1886454
--- /dev/null
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
@@ -0,0 +1,230 @@
+/*
+ * 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.demo.embedding
+
+import android.content.Context
+import androidx.startup.Initializer
+import androidx.window.core.ExperimentalWindowApi
+import androidx.window.demo.R
+import androidx.window.demo.embedding.SplitDeviceStateActivityBase.Companion.SUFFIX_AND_FULLSCREEN_IN_BOOK_MODE
+import androidx.window.demo.embedding.SplitDeviceStateActivityBase.Companion.SUFFIX_AND_HORIZONTAL_LAYOUT_IN_TABLETOP
+import androidx.window.demo.embedding.SplitDeviceStateActivityBase.Companion.SUFFIX_REVERSED
+import androidx.window.demo.embedding.SplitDeviceStateActivityBase.Companion.TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE
+import androidx.window.demo.embedding.SplitDeviceStateActivityBase.Companion.TAG_SHOW_FULLSCREEN_IN_PORTRAIT
+import androidx.window.demo.embedding.SplitDeviceStateActivityBase.Companion.TAG_SHOW_HORIZONTAL_LAYOUT_IN_TABLETOP
+import androidx.window.demo.embedding.SplitDeviceStateActivityBase.Companion.TAG_SHOW_LAYOUT_FOLLOWING_HINGE_WHEN_SEPARATING
+import androidx.window.demo.embedding.SplitDeviceStateActivityBase.Companion.TAG_USE_DEFAULT_SPLIT_ATTRIBUTES
+import androidx.window.embedding.RuleController
+import androidx.window.embedding.SplitAttributes
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.BOTTOM_TO_TOP
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LEFT_TO_RIGHT
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.RIGHT_TO_LEFT
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.TOP_TO_BOTTOM
+import androidx.window.embedding.SplitAttributesCalculatorParams
+import androidx.window.embedding.SplitController
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowLayoutInfo
+import androidx.window.layout.WindowMetrics
+
+/**
+ * Initializes SplitController with a set of statically defined rules.
+ */
+@OptIn(ExperimentalWindowApi::class)
+class ExampleWindowInitializer : Initializer<RuleController> {
+ private val mDemoActivityEmbeddingController = DemoActivityEmbeddingController.getInstance()
+
+ override fun create(context: Context): RuleController {
+ SplitController.getInstance(context).apply {
+ if (isSplitAttributesCalculatorSupported()) {
+ setSplitAttributesCalculator(::sampleSplitAttributesCalculator)
+ }
+ }
+ return RuleController.getInstance(context).apply {
+ if (SplitController.getInstance(context).isSplitSupported()) {
+ setRules(RuleController.parseRules(context, R.xml.main_split_config))
+ }
+ }
+ }
+
+ /**
+ * A sample callback set in [SplitController.setSplitAttributesCalculator] to demonstrate how to
+ * change the [SplitAttributes] with the current device and window state and
+ * [SplitAttributesCalculatorParams.splitRuleTag].
+ */
+ private fun sampleSplitAttributesCalculator(
+ params: SplitAttributesCalculatorParams
+ ): SplitAttributes {
+ val isPortrait = params.parentWindowMetrics.isPortrait()
+ val windowLayoutInfo = params.parentWindowLayoutInfo
+ val isTabletop = windowLayoutInfo.isTabletop()
+ val isBookMode = windowLayoutInfo.isBookMode()
+ val config = params.parentConfiguration
+ // The SplitAttributes to occupy the whole task bounds
+ val expandContainersAttrs = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.expandContainers())
+ .build()
+ val tag = params.splitRuleTag
+ val shouldReversed = tag?.contains(SUFFIX_REVERSED) ?: false
+ // Make a copy of the default splitAttributes, but replace the animation background
+ // color to what is configured in the Demo app.
+ val backgroundColor = mDemoActivityEmbeddingController.animationBackgroundColor
+ val defaultSplitAttributes = SplitAttributes.Builder()
+ .setLayoutDirection(params.defaultSplitAttributes.layoutDirection)
+ .setSplitType(params.defaultSplitAttributes.splitType)
+ .setAnimationBackgroundColor(backgroundColor)
+ .build()
+ when (tag?.substringBefore(SUFFIX_REVERSED)) {
+ TAG_USE_DEFAULT_SPLIT_ATTRIBUTES, null -> {
+ return if (params.areDefaultConstraintsSatisfied) {
+ defaultSplitAttributes
+ } else {
+ expandContainersAttrs
+ }
+ }
+ TAG_SHOW_FULLSCREEN_IN_PORTRAIT -> {
+ if (isPortrait) {
+ return expandContainersAttrs
+ }
+ }
+ TAG_SHOW_FULLSCREEN_IN_PORTRAIT + SUFFIX_AND_HORIZONTAL_LAYOUT_IN_TABLETOP -> {
+ if (isTabletop) {
+ return SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.splitByHinge())
+ .setLayoutDirection(
+ if (shouldReversed) {
+ BOTTOM_TO_TOP
+ } else {
+ TOP_TO_BOTTOM
+ }
+ )
+ .setAnimationBackgroundColor(backgroundColor)
+ .build()
+ } else if (isPortrait) {
+ return expandContainersAttrs
+ }
+ }
+ TAG_SHOW_HORIZONTAL_LAYOUT_IN_TABLETOP -> {
+ if (isTabletop) {
+ return SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.splitByHinge())
+ .setLayoutDirection(
+ if (shouldReversed) {
+ BOTTOM_TO_TOP
+ } else {
+ TOP_TO_BOTTOM
+ }
+ )
+ .setAnimationBackgroundColor(backgroundColor)
+ .build()
+ }
+ }
+ TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE -> {
+ return SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.splitByHinge())
+ .setLayoutDirection(
+ if (shouldReversed) {
+ BOTTOM_TO_TOP
+ } else {
+ TOP_TO_BOTTOM
+ }
+ ).build()
+ }
+ TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE + SUFFIX_AND_FULLSCREEN_IN_BOOK_MODE -> {
+ return if (isBookMode) {
+ expandContainersAttrs
+ } else if (config.screenWidthDp <= 600) {
+ SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.splitEqually())
+ .setLayoutDirection(
+ if (shouldReversed) {
+ BOTTOM_TO_TOP
+ } else {
+ TOP_TO_BOTTOM
+ }
+ )
+ .setAnimationBackgroundColor(backgroundColor)
+ .build()
+ } else {
+ SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.splitEqually())
+ .setLayoutDirection(
+ if (shouldReversed) {
+ RIGHT_TO_LEFT
+ } else {
+ LEFT_TO_RIGHT
+ }
+ )
+ .setAnimationBackgroundColor(backgroundColor)
+ .build()
+ }
+ }
+ TAG_SHOW_LAYOUT_FOLLOWING_HINGE_WHEN_SEPARATING -> {
+ val foldingState = windowLayoutInfo.getFoldingFeature()
+ if (foldingState != null) {
+ return SplitAttributes.Builder()
+ .setSplitType(
+ if (foldingState.isSeparating) {
+ SplitAttributes.SplitType.splitByHinge()
+ } else {
+ SplitAttributes.SplitType.ratio(0.3f)
+ }
+ ).setLayoutDirection(
+ if (
+ foldingState.orientation
+ == FoldingFeature.Orientation.HORIZONTAL
+ ) {
+ if (shouldReversed) BOTTOM_TO_TOP else TOP_TO_BOTTOM
+ } else {
+ if (shouldReversed) RIGHT_TO_LEFT else LEFT_TO_RIGHT
+ }
+ )
+ .setAnimationBackgroundColor(backgroundColor)
+ .build()
+ }
+ }
+ }
+ return defaultSplitAttributes
+ }
+
+ private fun WindowMetrics.isPortrait(): Boolean =
+ bounds.height() > bounds.width()
+
+ private fun WindowLayoutInfo.isTabletop(): Boolean {
+ val foldingFeature = getFoldingFeature()
+ return foldingFeature?.state == FoldingFeature.State.HALF_OPENED &&
+ foldingFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
+ }
+
+ private fun WindowLayoutInfo.isBookMode(): Boolean {
+ val foldingFeature = getFoldingFeature()
+ return foldingFeature?.state == FoldingFeature.State.HALF_OPENED &&
+ foldingFeature.orientation == FoldingFeature.Orientation.VERTICAL
+ }
+
+ /**
+ * Returns the [FoldingFeature] if it is exactly the only [FoldingFeature] in
+ * [WindowLayoutInfo]. Otherwise, returns `null`.
+ */
+ private fun WindowLayoutInfo.getFoldingFeature(): FoldingFeature? {
+ val foldingFeatures = displayFeatures.filterIsInstance<FoldingFeature>()
+ return if (foldingFeatures.size == 1) foldingFeatures[0] else null
+ }
+
+ override fun dependencies(): List<Class<out Initializer<*>>> {
+ return emptyList()
+ }
+}
\ No newline at end of file
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExpandedDialogActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExpandedDialogActivity.kt
new file mode 100644
index 0000000..3419fcb
--- /dev/null
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExpandedDialogActivity.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.demo.embedding
+
+import android.os.Bundle
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+
+/** Activity to show a dialog. */
+class ExpandedDialogActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ showDialog()
+ }
+
+ private fun showDialog() {
+ val dialog = AlertDialog.Builder(this)
+ .setTitle("Dialog in expanded activity")
+ .setMessage("To demo showing dialog that can expand over a split")
+ .setNeutralButton("Close") { _, _ ->
+ finish()
+ }
+ .setOnDismissListener {
+ finish()
+ }
+
+ dialog.show()
+ }
+}
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityA.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityA.kt
similarity index 93%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityA.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityA.kt
index 20a8056..94757d6 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityA.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityA.kt
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
open class SplitActivityA : SplitActivityBase()
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityB.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityB.kt
similarity index 94%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityB.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityB.kt
index 6791b5c..90a67dd 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityB.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityB.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.View
-import androidx.window.sample.R
+import androidx.window.demo.R
open class SplitActivityB : SplitActivityBase() {
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java
similarity index 62%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java
index c3acc7a..59ca4ac 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java
@@ -14,35 +14,42 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding;
+package androidx.window.demo.embedding;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static androidx.window.embedding.SplitRule.FinishBehavior.ADJACENT;
+import static androidx.window.embedding.SplitRule.FinishBehavior.ALWAYS;
+import static androidx.window.embedding.SplitRule.FinishBehavior.NEVER;
+
import android.app.Activity;
import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.util.Consumer;
+import androidx.window.demo.R;
+import androidx.window.demo.databinding.ActivitySplitActivityLayoutBinding;
import androidx.window.embedding.ActivityEmbeddingController;
import androidx.window.embedding.ActivityFilter;
import androidx.window.embedding.ActivityRule;
import androidx.window.embedding.EmbeddingRule;
import androidx.window.embedding.RuleController;
+import androidx.window.embedding.SplitAttributes;
import androidx.window.embedding.SplitController;
import androidx.window.embedding.SplitInfo;
import androidx.window.embedding.SplitPairFilter;
import androidx.window.embedding.SplitPairRule;
import androidx.window.embedding.SplitPlaceholderRule;
-import androidx.window.embedding.SplitRule;
-import androidx.window.sample.databinding.ActivitySplitActivityLayoutBinding;
import java.util.HashSet;
import java.util.List;
@@ -61,9 +68,7 @@
static final String EXTRA_LAUNCH_C_TO_SIDE = "launch_c_to_side";
private SplitController mSplitController;
-
- private final RuleController mRuleController = RuleController.getInstance(this);
-
+ private RuleController mRuleController;
private SplitInfoCallback mCallback;
private ActivitySplitActivityLayoutBinding mViewBinding;
@@ -77,7 +82,7 @@
mViewBinding = ActivitySplitActivityLayoutBinding.inflate(getLayoutInflater());
setContentView(mViewBinding.getRoot());
- // Setup activity launch buttons.
+ // Setup activity launch buttons and config options.
mViewBinding.launchB.setOnClickListener((View v) ->
startActivity(new Intent(this, SplitActivityB.class)));
mViewBinding.launchBAndC.setOnClickListener((View v) -> {
@@ -97,6 +102,50 @@
Log.e(TAG, e.getMessage());
}
});
+ mViewBinding.launchUid2Trusted.setOnClickListener((View v) -> {
+ final Intent intent = new Intent();
+ // Use an explicit package and class name to start an Activity from a different
+ // package/UID.
+ intent.setClassName(
+ "androidx.window.demo2",
+ "androidx.window.demo2.embedding.TrustedEmbeddingActivity"
+ );
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.install_samples_2, Toast.LENGTH_LONG).show();
+ }
+ });
+ mViewBinding.launchUid2Untrusted.setOnClickListener((View v) -> {
+ final Intent intent = new Intent();
+ // Use an explicit package and class name to start an Activity from a different
+ // package/UID.
+ intent.setClassName(
+ "androidx.window.demo2",
+ "androidx.window.demo2.embedding.UntrustedEmbeddingActivity"
+ );
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.install_samples_2, Toast.LENGTH_LONG).show();
+ }
+ });
+ mViewBinding.launchUid2UntrustedDisplayFeatures.setOnClickListener((View v) -> {
+ final Intent intent = new Intent();
+ // Use an explicit package and class name to start an Activity from a different
+ // package/UID.
+ intent.setClassName(
+ "androidx.window.demo2",
+ "androidx.window.demo.common.DisplayFeaturesActivity"
+ );
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.install_samples_2, Toast.LENGTH_LONG).show();
+ }
+ });
+ mViewBinding.launchExpandedDialogButton.setOnClickListener((View v) ->
+ startActivity(new Intent(this, ExpandedDialogActivity.class)));
// Listen for split configuration checkboxes to update the rules before launching
// activities.
@@ -109,6 +158,13 @@
mViewBinding.splitWithFCheckBox.setOnCheckedChangeListener(this);
mSplitController = SplitController.getInstance(this);
+ if (!mSplitController.isSplitSupported()) {
+ Toast.makeText(this, R.string.toast_split_not_support,
+ Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+ mRuleController = RuleController.getInstance(this);
}
@Override
@@ -177,8 +233,8 @@
mViewBinding.splitBCCheckBox.setChecked(bAndCPairConfig != null);
mViewBinding.finishBCCheckBox.setEnabled(bAndCPairConfig != null);
mViewBinding.finishBCCheckBox.setChecked(bAndCPairConfig != null
- && bAndCPairConfig.getFinishPrimaryWithSecondary() == SplitRule.FINISH_ALWAYS
- && bAndCPairConfig.getFinishSecondaryWithPrimary() == SplitRule.FINISH_ALWAYS);
+ && bAndCPairConfig.getFinishPrimaryWithSecondary() == ALWAYS
+ && bAndCPairConfig.getFinishSecondaryWithPrimary() == ALWAYS);
SplitPairRule fConfig = getRuleFor(null, SplitActivityF.class);
mViewBinding.splitWithFCheckBox.setChecked(fConfig != null);
@@ -259,85 +315,102 @@
/** Updates the split rules based on the current selection on checkboxes. */
private void updateRulesFromCheckboxes() {
mRuleController.clearRules();
-
- Set<SplitPairFilter> pairFilters = new HashSet<>();
- pairFilters.add(new SplitPairFilter(componentName(SplitActivityA.class),
- componentName("*"), null));
- SplitPairRule rule = new SplitPairRule.Builder(pairFilters)
- .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
- .setMinSmallestWidthDp(0)
- .setFinishPrimaryWithSecondary(SplitRule.FINISH_NEVER)
- .setFinishSecondaryWithPrimary(SplitRule.FINISH_NEVER)
- .setClearTop(true)
- .setSplitRatio(SPLIT_RATIO)
+ final SplitAttributes defaultSplitAttributes = new SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(SPLIT_RATIO))
.build();
+
if (mViewBinding.splitMainCheckBox.isChecked()) {
+ // Split main with any activity.
+ final Set<SplitPairFilter> pairFilters = new HashSet<>();
+ pairFilters.add(new SplitPairFilter(componentName(SplitActivityA.class),
+ new ComponentName("*", "*"), null));
+ final SplitPairRule rule = new SplitPairRule.Builder(pairFilters)
+ .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+ .setMinHeightDp(0)
+ .setMinSmallestWidthDp(0)
+ .setFinishPrimaryWithSecondary(NEVER)
+ .setFinishSecondaryWithPrimary(NEVER)
+ .setClearTop(true)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
+ .build();
mRuleController.addRule(rule);
}
- Set<ActivityFilter> activityFilters = new HashSet<>();
- activityFilters.add(new ActivityFilter(componentName(SplitActivityB.class), null));
- Intent intent = new Intent();
- intent.setComponent(
- componentName("androidx.window.sample.embedding.SplitActivityPlaceholder"));
- SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder(
- activityFilters,
- intent
- )
- .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
- .setMinSmallestWidthDp(0)
- .setSticky(mViewBinding.useStickyPlaceholderCheckBox.isChecked())
- .setFinishPrimaryWithPlaceholder(SplitRule.FINISH_ADJACENT)
- .setSplitRatio(SPLIT_RATIO)
- .build();
if (mViewBinding.usePlaceholderCheckBox.isChecked()) {
- mRuleController.addRule(placeholderRule);
+ // Split B with placeholder.
+ final Set<ActivityFilter> activityFilters = new HashSet<>();
+ activityFilters.add(new ActivityFilter(componentName(SplitActivityB.class), null));
+ final Intent intent = new Intent();
+ intent.setComponent(componentName(SplitActivityPlaceholder.class));
+ final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(
+ activityFilters,
+ intent
+ )
+ .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+ .setMinHeightDp(0)
+ .setMinSmallestWidthDp(0)
+ .setSticky(mViewBinding.useStickyPlaceholderCheckBox.isChecked())
+ .setFinishPrimaryWithPlaceholder(ADJACENT)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
+ .build();
+ mRuleController.addRule(rule);
}
- pairFilters = new HashSet<>();
- pairFilters.add(new SplitPairFilter(componentName(SplitActivityB.class),
- componentName(SplitActivityC.class), null));
- rule = new SplitPairRule.Builder(pairFilters)
- .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
- .setMinSmallestWidthDp(0)
- .setFinishPrimaryWithSecondary(
- mViewBinding.finishBCCheckBox.isChecked()
- ? SplitRule.FINISH_ALWAYS : SplitRule.FINISH_NEVER
- )
- .setFinishSecondaryWithPrimary(
- mViewBinding.finishBCCheckBox.isChecked()
- ? SplitRule.FINISH_ALWAYS : SplitRule.FINISH_NEVER
- )
- .setClearTop(true)
- .setSplitRatio(SPLIT_RATIO)
- .build();
if (mViewBinding.splitBCCheckBox.isChecked()) {
+ // Split B with C.
+ final Set<SplitPairFilter> pairFilters = new HashSet<>();
+ pairFilters.add(new SplitPairFilter(componentName(SplitActivityB.class),
+ componentName(SplitActivityC.class), null));
+ final SplitPairRule rule = new SplitPairRule.Builder(pairFilters)
+ .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+ .setMinHeightDp(0)
+ .setMinSmallestWidthDp(0)
+ .setFinishPrimaryWithSecondary(
+ mViewBinding.finishBCCheckBox.isChecked() ? ALWAYS : NEVER
+ )
+ .setFinishSecondaryWithPrimary(
+ mViewBinding.finishBCCheckBox.isChecked() ? ALWAYS : NEVER
+ )
+ .setClearTop(true)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
+ .build();
mRuleController.addRule(rule);
}
- pairFilters = new HashSet<>();
- pairFilters.add(new SplitPairFilter(componentName("androidx.window.*"),
- componentName(SplitActivityF.class), null));
- rule = new SplitPairRule.Builder(pairFilters)
- .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
- .setMinSmallestWidthDp(0)
- .setFinishPrimaryWithSecondary(SplitRule.FINISH_NEVER)
- .setFinishSecondaryWithPrimary(SplitRule.FINISH_NEVER)
- .setClearTop(true)
- .setSplitRatio(SPLIT_RATIO)
- .build();
if (mViewBinding.splitWithFCheckBox.isChecked()) {
+ // Split any activity with F.
+ final Set<SplitPairFilter> pairFilters = new HashSet<>();
+ pairFilters.add(new SplitPairFilter(new ComponentName("*", "*"),
+ componentName(SplitActivityF.class), null));
+ final SplitPairRule rule = new SplitPairRule.Builder(pairFilters)
+ .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+ .setMinHeightDp(0)
+ .setMinSmallestWidthDp(0)
+ .setFinishPrimaryWithSecondary(NEVER)
+ .setFinishSecondaryWithPrimary(NEVER)
+ .setClearTop(true)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
+ .build();
mRuleController.addRule(rule);
}
- activityFilters = new HashSet<>();
- activityFilters.add(new ActivityFilter(componentName(SplitActivityE.class), null));
- ActivityRule activityRule = new ActivityRule.Builder(activityFilters)
- .setAlwaysExpand(true)
- .build();
if (mViewBinding.fullscreenECheckBox.isChecked()) {
+ // Launch E in fullscreen.
+ final Set<ActivityFilter> activityFilters = new HashSet<>();
+ activityFilters.add(new ActivityFilter(componentName(SplitActivityE.class), null));
+ final ActivityRule activityRule = new ActivityRule.Builder(activityFilters)
+ .setAlwaysExpand(true)
+ .build();
mRuleController.addRule(activityRule);
}
+
+ // Always expand the dialog activity.
+ final Set<ActivityFilter> dialogActivityFilters = new HashSet<>();
+ dialogActivityFilters.add(new ActivityFilter(componentName(
+ ExpandedDialogActivity.class), null));
+ mRuleController.addRule(new ActivityRule.Builder(dialogActivityFilters)
+ .setAlwaysExpand(true)
+ .build());
}
ComponentName componentName(Class<? extends Activity> activityClass) {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityC.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityC.kt
similarity index 92%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityC.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityC.kt
index 413c6c3..dcbbb3b 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityC.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityC.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.graphics.Color
import android.os.Bundle
import android.view.View
-import androidx.window.sample.R
+import androidx.window.demo.R
open class SplitActivityC : SplitActivityBase() {
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityD.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityD.kt
similarity index 92%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityD.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityD.kt
index 9f919f0..a9ec11a 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityD.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityD.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.graphics.Color
import android.os.Bundle
import android.view.View
-import androidx.window.sample.R
+import androidx.window.demo.R
open class SplitActivityD : SplitActivityBase() {
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityDetail.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityDetail.kt
similarity index 95%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityDetail.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityDetail.kt
index 6c4897e..5db4314 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityDetail.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityDetail.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.content.Intent
import android.graphics.Color
@@ -22,7 +22,7 @@
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
-import androidx.window.sample.R
+import androidx.window.demo.R
open class SplitActivityDetail : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityE.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityE.kt
similarity index 92%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityE.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityE.kt
index 9960ef2..17c43b1 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityE.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityE.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.graphics.Color
import android.os.Bundle
import android.view.View
-import androidx.window.sample.R
+import androidx.window.demo.R
open class SplitActivityE : SplitActivityBase() {
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityF.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityF.kt
similarity index 92%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityF.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityF.kt
index 3243e5d..325524e 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityF.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityF.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.graphics.Color
import android.os.Bundle
import android.view.View
-import androidx.window.sample.R
+import androidx.window.demo.R
open class SplitActivityF : SplitActivityBase() {
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityList.kt
similarity index 88%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityList.kt
index cb64edf..49e119a 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityList.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.content.Intent
import android.graphics.Color
@@ -26,12 +26,12 @@
import androidx.core.util.Consumer
import androidx.window.embedding.SplitController
import androidx.window.embedding.SplitInfo
-import androidx.window.sample.R
-import androidx.window.sample.embedding.SplitActivityDetail.Companion.EXTRA_SELECTED_ITEM
+import androidx.window.demo.R
+import androidx.window.demo.embedding.SplitActivityDetail.Companion.EXTRA_SELECTED_ITEM
open class SplitActivityList : AppCompatActivity() {
- private lateinit var splitController: SplitController
- private val splitChangeListener = SplitStateChangeListener()
+ lateinit var splitController: SplitController
+ val splitChangeListener = SplitStateChangeListener()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityListPlaceholder.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityListPlaceholder.kt
similarity index 93%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityListPlaceholder.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityListPlaceholder.kt
index 754e11d8..ab08fe7 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityListPlaceholder.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityListPlaceholder.kt
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
open class SplitActivityListPlaceholder : SplitActivityPlaceholder()
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityPlaceholder.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityPlaceholder.kt
similarity index 89%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityPlaceholder.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityPlaceholder.kt
index 7314921..380b5b7 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityPlaceholder.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityPlaceholder.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
-import androidx.window.sample.databinding.ActivitySplitActivityPlaceholderLayoutBinding
+import androidx.window.demo.databinding.ActivitySplitActivityPlaceholderLayoutBinding
open class SplitActivityPlaceholder : AppCompatActivity() {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityTrampoline.kt
similarity index 69%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityTrampoline.kt
index 212cbcb..3732156 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityTrampoline.kt
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.content.Intent
import android.os.Bundle
import androidx.window.embedding.ActivityFilter
import androidx.window.embedding.RuleController
+import androidx.window.embedding.SplitAttributes
import androidx.window.embedding.SplitPlaceholderRule
-import androidx.window.embedding.SplitRule.Companion.FINISH_ADJACENT
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.ADJACENT
/**
* Example trampoline activity that launches a split and finishes itself.
@@ -31,20 +32,24 @@
super.onCreate(savedInstanceState)
val activityFilters = setOf(ActivityFilter(componentName(
- "androidx.window.sample.embedding.SplitActivityTrampolineTarget"), null))
+ "androidx.window.demo.embedding.SplitActivityTrampolineTarget"), null))
val placeholderIntent = Intent()
placeholderIntent.component =
- componentName("androidx.window.sample.embedding.SplitActivityPlaceholder")
+ componentName("androidx.window.demo.embedding.SplitActivityPlaceholder")
+ val defaultSplitAttributes = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(SPLIT_RATIO))
+ .build()
val placeholderRule = SplitPlaceholderRule.Builder(activityFilters, placeholderIntent)
.setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+ .setMinHeightDp(0)
.setMinSmallestWidthDp(0)
- .setFinishPrimaryWithPlaceholder(FINISH_ADJACENT)
- .setSplitRatio(SPLIT_RATIO)
+ .setFinishPrimaryWithPlaceholder(ADJACENT)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
.build()
RuleController.getInstance(this).addRule(placeholderRule)
val activityIntent = Intent()
activityIntent.component = componentName(
- "androidx.window.sample.embedding.SplitActivityTrampolineTarget")
+ "androidx.window.demo.embedding.SplitActivityTrampolineTarget")
startActivity(activityIntent)
finish()
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityA.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityA.kt
similarity index 85%
copy from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityA.kt
copy to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityA.kt
index 7053e2d..9ba2ac3 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityA.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityA.kt
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
-open class SplitPipActivityA : SplitPipActivityBase()
\ No newline at end of file
+class SplitDeviceStateActivityA : SplitDeviceStateActivityBase()
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityC.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityB.kt
similarity index 76%
copy from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityC.kt
copy to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityB.kt
index 413c6c3..6711d76 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityC.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityB.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.graphics.Color
import android.os.Bundle
import android.view.View
-import androidx.window.sample.R
+import androidx.window.demo.R
-open class SplitActivityC : SplitActivityBase() {
+class SplitDeviceStateActivityB : SplitDeviceStateActivityBase() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findViewById<View>(R.id.root_split_activity_layout)
- .setBackgroundColor(Color.parseColor("#e8f5e9"))
+ .setBackgroundColor(Color.parseColor("#fff3e0"))
}
-}
+}
\ No newline at end of file
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
new file mode 100644
index 0000000..5f686b2
--- /dev/null
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
@@ -0,0 +1,430 @@
+/*
+ * 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.demo.embedding
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.CompoundButton
+import android.widget.RadioGroup
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.core.util.Consumer
+import androidx.window.core.ExperimentalWindowApi
+import androidx.window.embedding.EmbeddingRule
+import androidx.window.embedding.SplitAttributes
+import androidx.window.embedding.SplitController
+import androidx.window.embedding.SplitInfo
+import androidx.window.embedding.SplitPairFilter
+import androidx.window.embedding.SplitPairRule
+import androidx.window.demo.R
+import androidx.window.demo.databinding.ActivitySplitDeviceStateLayoutBinding
+import androidx.window.embedding.RuleController
+
+open class SplitDeviceStateActivityBase : AppCompatActivity(), View.OnClickListener,
+ RadioGroup.OnCheckedChangeListener, CompoundButton.OnCheckedChangeListener,
+ AdapterView.OnItemSelectedListener {
+
+ private lateinit var splitController: SplitController
+ private lateinit var ruleController: RuleController
+
+ private val splitStateChangeListener = SplitStateChangeListener()
+
+ private lateinit var splitPairRule: SplitPairRule
+ private var shouldReverseContainerPosition = false
+ private var shouldShowHorizontalInTabletop = false
+ private var shouldShowFullscreenInBookMode = false
+
+ private lateinit var viewBinding: ActivitySplitDeviceStateLayoutBinding
+ private lateinit var activityA: ComponentName
+ private lateinit var activityB: ComponentName
+
+ /** Controller to manage the global configuration. */
+ private val demoActivityEmbeddingController = DemoActivityEmbeddingController.getInstance()
+
+ /** The last selected split rule id. */
+ private var lastCheckedRuleId = 0
+
+ @OptIn(ExperimentalWindowApi::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ viewBinding = ActivitySplitDeviceStateLayoutBinding.inflate(layoutInflater)
+ splitController = SplitController.getInstance(this)
+ if (!splitController.isSplitSupported()) {
+ Toast.makeText(
+ this, R.string.toast_split_not_support,
+ Toast.LENGTH_SHORT
+ ).show()
+ finish()
+ return
+ }
+ ruleController = RuleController.getInstance(this)
+
+ setContentView(viewBinding.root)
+
+ activityA = ComponentName(this, SplitDeviceStateActivityA::class.java.name)
+ activityB = ComponentName(this, SplitDeviceStateActivityB::class.java.name)
+
+ val radioGroup = viewBinding.splitAttributesOptionsRadioGroup
+ val animationBgColorDropdown = viewBinding.animationBackgroundColorDropdown
+ if (componentName == activityA) {
+ // Set to the first option
+ demoActivityEmbeddingController.animationBackgroundColor =
+ ANIMATION_BACKGROUND_COLORS_VALUE[0]
+ radioGroup.check(R.id.use_default_split_attributes)
+ onCheckedChanged(radioGroup, radioGroup.checkedRadioButtonId)
+ radioGroup.setOnCheckedChangeListener(this)
+ animationBgColorDropdown.adapter = ArrayAdapter(
+ this,
+ android.R.layout.simple_spinner_dropdown_item,
+ ANIMATION_BACKGROUND_COLORS_TEXT
+ )
+ animationBgColorDropdown.>
+ } else {
+ // Only update split pair rule on the primary Activity. The secondary Activity can only
+ // finish itself to prevent confusing users. We only apply the rule when the Activity is
+ // launched from the primary.
+ viewBinding.chooseLayoutTextView.visibility = View.GONE
+ radioGroup.visibility = View.GONE
+ animationBgColorDropdown.visibility = View.GONE
+ viewBinding.launchActivityToSide.text = "Finish this Activity"
+ }
+
+ viewBinding.showHorizontalLayoutInTabletopCheckBox.setOnCheckedChangeListener(this)
+ viewBinding.showFullscreenInBookModeCheckBox.setOnCheckedChangeListener(this)
+ viewBinding.swapPrimarySecondaryPositionCheckBox.setOnCheckedChangeListener(this)
+ viewBinding.launchActivityToSide.setOnClickListener(this)
+
+ val isCallbackSupported = splitController.isSplitAttributesCalculatorSupported()
+ if (!isCallbackSupported) {
+ // Disable the radioButtons that use SplitAttributesCalculator
+ viewBinding.showFullscreenInPortraitRadioButton.isEnabled = false
+ viewBinding.showHorizontalLayoutInTabletopRadioButton.isEnabled = false
+ viewBinding.showDifferentLayoutWithSizeRadioButton.isEnabled = false
+ viewBinding.splitByHingeWhenSeparatingRadioButton.isEnabled = false
+ hideAllSubCheckBoxes()
+ // Add the error message to notify the SplitAttributesCalculator is not available.
+ viewBinding.errorMessageTextView.text = "SplitAttributesCalculator is not supported!"
+ animationBgColorDropdown.isEnabled = false
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ splitController.addSplitListener(
+ this,
+ ContextCompat.getMainExecutor(this),
+ splitStateChangeListener
+ )
+ }
+
+ override fun onStop() {
+ super.onStop()
+ splitController.removeSplitListener(splitStateChangeListener)
+ }
+
+ override fun onClick(button: View) {
+ if (button.id != R.id.launch_activity_to_side) {
+ return
+ }
+ when (componentName) {
+ activityA -> {
+ startActivity(Intent(this, SplitDeviceStateActivityB::class.java))
+ }
+ activityB -> finish()
+ }
+ }
+
+ override fun onCheckedChanged(c: CompoundButton, isChecked: Boolean) {
+ when (c.id) {
+ R.id.swap_primary_secondary_position_check_box -> {
+ shouldReverseContainerPosition = isChecked
+ updateSplitPairRuleWithRadioButtonId(
+ viewBinding.splitAttributesOptionsRadioGroup.checkedRadioButtonId
+ )
+ }
+ R.id.show_horizontal_layout_in_tabletop_check_box -> {
+ shouldShowHorizontalInTabletop = isChecked
+ updateSplitPairRuleWithRadioButtonId(
+ R.id.show_fullscreen_in_portrait_radio_button
+ )
+ }
+ R.id.show_fullscreen_in_book_mode_check_box -> {
+ shouldShowFullscreenInBookMode = isChecked
+ updateSplitPairRuleWithRadioButtonId(
+ R.id.show_different_layout_with_size_radio_button
+ )
+ }
+ }
+ }
+
+ override fun onCheckedChanged(group: RadioGroup, id: Int) {
+ updateCheckboxWithRadioButton(id)
+ updateSplitPairRuleWithRadioButtonId(id)
+ }
+
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ demoActivityEmbeddingController.animationBackgroundColor =
+ ANIMATION_BACKGROUND_COLORS_VALUE[position]
+ updateSplitPairRuleWithRadioButtonId(lastCheckedRuleId)
+ }
+
+ override fun onNothingSelected(view: AdapterView<*>?) {
+ // Auto-generated method stub
+ }
+
+ private fun updateCheckboxWithRadioButton(id: Int) {
+ when (id) {
+ R.id.show_fullscreen_in_portrait_radio_button -> {
+ showCheckBox(R.id.show_horizontal_layout_in_tabletop_check_box)
+ hideCheckBox(R.id.show_fullscreen_in_book_mode_check_box)
+ }
+ R.id.show_different_layout_with_size_radio_button -> {
+ hideCheckBox(R.id.show_horizontal_layout_in_tabletop_check_box)
+ showCheckBox(R.id.show_fullscreen_in_book_mode_check_box)
+ }
+ else -> hideAllSubCheckBoxes()
+ }
+ // Disable the checkbox because this won't be applied if users want to use the default rule
+ // behavior.
+ viewBinding.swapPrimarySecondaryPositionCheckBox.isEnabled =
+ id != R.id.use_default_split_attributes
+ }
+
+ private fun hideAllSubCheckBoxes() {
+ hideCheckBox(R.id.show_horizontal_layout_in_tabletop_check_box)
+ hideCheckBox(R.id.show_fullscreen_in_book_mode_check_box)
+ }
+
+ /** Show check box with [id] and also hides other check boxes. */
+ private fun showCheckBox(id: Int) {
+ when (id) {
+ R.id.show_horizontal_layout_in_tabletop_check_box -> {
+ viewBinding.showFullscreenInPortraitDividerTop.visibility = View.VISIBLE
+ viewBinding.showHorizontalLayoutInTabletopCheckBox.visibility = View.VISIBLE
+ viewBinding.showFullscreenInPortraitDividerBottom.visibility = View.VISIBLE
+ }
+ R.id.show_fullscreen_in_book_mode_check_box -> {
+ viewBinding.showDifferentLayoutWithSizeDividerTop.visibility = View.VISIBLE
+ viewBinding.showFullscreenInBookModeCheckBox.visibility = View.VISIBLE
+ viewBinding.showDifferentLayoutWithSizeDividerBottom.visibility = View.VISIBLE
+ }
+ }
+ }
+
+ private fun hideCheckBox(id: Int) {
+ when (id) {
+ R.id.show_horizontal_layout_in_tabletop_check_box -> {
+ viewBinding.showFullscreenInPortraitDividerTop.visibility = View.GONE
+ viewBinding.showHorizontalLayoutInTabletopCheckBox.visibility = View.GONE
+ viewBinding.showFullscreenInPortraitDividerBottom.visibility = View.GONE
+ shouldShowHorizontalInTabletop = false
+ }
+ R.id.show_fullscreen_in_book_mode_check_box -> {
+ viewBinding.showDifferentLayoutWithSizeDividerTop.visibility = View.GONE
+ viewBinding.showFullscreenInBookModeCheckBox.visibility = View.GONE
+ viewBinding.showDifferentLayoutWithSizeDividerBottom.visibility = View.GONE
+ shouldShowFullscreenInBookMode = false
+ }
+ }
+ }
+
+ private fun updateSplitPairRuleWithRadioButtonId(id: Int) {
+ lastCheckedRuleId = id
+ ruleController.clearRules()
+
+ val splitPairFilters = HashSet<SplitPairFilter>()
+ val splitPairFilter = SplitPairFilter(
+ activityA,
+ activityB,
+ secondaryActivityIntentAction = null
+ )
+ splitPairFilters.add(splitPairFilter)
+ val defaultSplitAttributes = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+ .setAnimationBackgroundColor(demoActivityEmbeddingController.animationBackgroundColor)
+ .build()
+ // Use the tag to control the rule how to change split attributes with the current state
+ var tag = when (id) {
+ R.id.use_default_split_attributes -> TAG_USE_DEFAULT_SPLIT_ATTRIBUTES
+ R.id.show_fullscreen_in_portrait_radio_button -> {
+ if (shouldShowHorizontalInTabletop) {
+ TAG_SHOW_FULLSCREEN_IN_PORTRAIT + SUFFIX_AND_HORIZONTAL_LAYOUT_IN_TABLETOP
+ } else {
+ TAG_SHOW_FULLSCREEN_IN_PORTRAIT
+ }
+ }
+ R.id.show_horizontal_layout_in_tabletop_radio_button -> {
+ if (shouldReverseContainerPosition) {
+ TAG_SHOW_HORIZONTAL_LAYOUT_IN_TABLETOP + SUFFIX_REVERSED
+ } else {
+ TAG_SHOW_HORIZONTAL_LAYOUT_IN_TABLETOP
+ }
+ }
+ R.id.show_different_layout_with_size_radio_button -> {
+ if (shouldShowFullscreenInBookMode) {
+ TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE + SUFFIX_AND_FULLSCREEN_IN_BOOK_MODE
+ } else {
+ TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE
+ }
+ }
+ R.id.split_by_hinge_when_separating_radio_button ->
+ TAG_SHOW_LAYOUT_FOLLOWING_HINGE_WHEN_SEPARATING
+ else -> null
+ }
+ if (shouldReverseContainerPosition) {
+ tag += SUFFIX_REVERSED
+ }
+
+ splitPairRule = SplitPairRule.Builder(splitPairFilters)
+ .setTag(tag)
+ .setMinWidthDp(DEFAULT_MINIMUM_WIDTH_DP)
+ .setMinSmallestWidthDp(DEFAULT_MINIMUM_WIDTH_DP)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
+ .build()
+ ruleController.addRule(splitPairRule)
+ }
+
+ /** Updates split attributes when receives callback from the extension. */
+ inner class SplitStateChangeListener : Consumer<List<SplitInfo>> {
+ override fun accept(newSplitInfos: List<SplitInfo>) {
+ updateSplitAttributesText(newSplitInfos)
+ updateRadioGroupAndCheckBoxFromRule()
+ }
+ }
+
+ @OptIn(ExperimentalWindowApi::class)
+ fun updateSplitAttributesText(newSplitInfos: List<SplitInfo>) {
+ var splitAttributes: SplitAttributes = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.expandContainers())
+ .build()
+ var suggestToFinishItself = false
+ val isCallbackSupported = splitController.isSplitAttributesCalculatorSupported()
+ // Traverse SplitInfos from the end because last SplitInfo has the highest z-order.
+ for (info in newSplitInfos.reversed()) {
+ if (info.contains(this@SplitDeviceStateActivityBase)) {
+ splitAttributes = info.splitAttributes
+ if (componentName == activityB &&
+ splitAttributes.splitType
+ is SplitAttributes.SplitType.ExpandContainersSplitType
+ ) {
+ // We don't put any functionality on activity B. Suggest users to finish the
+ // activity if it fills the host task.
+ suggestToFinishItself = true
+ }
+ break
+ }
+ }
+ runOnUiThread {
+ viewBinding.activityPairSplitAttributesTextView.text =
+ resources.getString(R.string.current_split_attributes) + splitAttributes
+ if (!isCallbackSupported) {
+ // Don't update the error message if the callback is not supported.
+ return@runOnUiThread
+ }
+ viewBinding.errorMessageTextView.text =
+ if (suggestToFinishItself) {
+ "Please finish the activity to try other split configurations."
+ } else {
+ ""
+ }
+ }
+ }
+
+ fun updateRadioGroupAndCheckBoxFromRule() {
+ val splitPairRule = ruleController.getRules().firstOrNull { rule ->
+ isRuleForSplitActivityA(rule)
+ } ?: return
+ val tag = splitPairRule.tag
+ viewBinding.splitAttributesOptionsRadioGroup.check(
+ when (tag?.substringBefore(SUFFIX_REVERSED)) {
+ TAG_USE_DEFAULT_SPLIT_ATTRIBUTES -> R.id.use_default_split_attributes
+ TAG_SHOW_FULLSCREEN_IN_PORTRAIT,
+ TAG_SHOW_FULLSCREEN_IN_PORTRAIT + SUFFIX_AND_HORIZONTAL_LAYOUT_IN_TABLETOP ->
+ R.id.show_fullscreen_in_portrait_radio_button
+ TAG_SHOW_HORIZONTAL_LAYOUT_IN_TABLETOP,
+ TAG_SHOW_HORIZONTAL_LAYOUT_IN_TABLETOP + SUFFIX_REVERSED ->
+ R.id.show_horizontal_layout_in_tabletop_radio_button
+ TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE,
+ TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE + SUFFIX_AND_FULLSCREEN_IN_BOOK_MODE ->
+ R.id.show_different_layout_with_size_radio_button
+ TAG_SHOW_LAYOUT_FOLLOWING_HINGE_WHEN_SEPARATING ->
+ R.id.split_by_hinge_when_separating_radio_button
+ else -> 0
+ }
+ )
+ if (tag?.contains(TAG_SHOW_FULLSCREEN_IN_PORTRAIT) == true) {
+ showCheckBox(R.id.show_horizontal_layout_in_tabletop_check_box)
+ viewBinding.showHorizontalLayoutInTabletopCheckBox.isChecked =
+ tag.contains(SUFFIX_AND_HORIZONTAL_LAYOUT_IN_TABLETOP)
+ } else if (tag?.contains(TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE) == true) {
+ showCheckBox(R.id.swap_primary_secondary_position_check_box)
+ viewBinding.showFullscreenInBookModeCheckBox.isChecked =
+ tag.contains(SUFFIX_AND_FULLSCREEN_IN_BOOK_MODE)
+ }
+
+ viewBinding.swapPrimarySecondaryPositionCheckBox.isChecked =
+ tag?.contains(SUFFIX_REVERSED) ?: false
+ }
+
+ private fun isRuleForSplitActivityA(rule: EmbeddingRule): Boolean {
+ if (rule !is SplitPairRule) {
+ return false
+ }
+ rule.filters.forEach { filter ->
+ if (filter.primaryActivityName == activityA &&
+ filter.secondaryActivityName == activityB
+ ) {
+ return true
+ }
+ }
+ return false
+ }
+
+ companion object {
+ const val TAG_USE_DEFAULT_SPLIT_ATTRIBUTES = "use_default_split_attributes"
+ const val TAG_SHOW_FULLSCREEN_IN_PORTRAIT = "show_fullscreen_in_portrait"
+ const val TAG_SHOW_HORIZONTAL_LAYOUT_IN_TABLETOP = "show_horizontal_layout_in_tabletop"
+ const val TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE = "show_different_layout_with_size"
+ const val TAG_SHOW_LAYOUT_FOLLOWING_HINGE_WHEN_SEPARATING = "show_layout_following_hinge"
+ const val SUFFIX_REVERSED = "_reversed"
+ const val SUFFIX_AND_HORIZONTAL_LAYOUT_IN_TABLETOP = "_and_horizontal_layout_in_tabletop"
+ const val SUFFIX_AND_FULLSCREEN_IN_BOOK_MODE = "_and_fullscreen_in_book_mode"
+ val ANIMATION_BACKGROUND_COLORS_TEXT = arrayOf("BLACK", "BLUE", "GREEN", "YELLOW")
+ val ANIMATION_BACKGROUND_COLORS_VALUE = arrayOf(
+ Color.BLACK,
+ Color.BLUE,
+ Color.GREEN,
+ Color.YELLOW
+ )
+
+ /**
+ * The default minimum dimension for large screen devices.
+ *
+ * It is also the default value of [SplitPairRule.minWidthDp] and
+ * [SplitPairRule.minSmallestWidthDp] if the properties are not specified in static rule
+ * XML format.
+ */
+ const val DEFAULT_MINIMUM_WIDTH_DP = 600
+ }
+}
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityA.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityA.kt
similarity index 93%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityA.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityA.kt
index 7053e2d..eaffe18 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityA.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityA.kt
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
open class SplitPipActivityA : SplitPipActivityBase()
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityB.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityB.kt
similarity index 95%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityB.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityB.kt
index f47ab24..2446624 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityB.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityB.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.graphics.Color
import android.os.Bundle
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityBase.kt
similarity index 87%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityBase.kt
index 9b76563..61c056c 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityBase.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.content.ComponentName
import android.content.Intent
@@ -27,19 +27,21 @@
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.util.Consumer
+import androidx.window.demo.R
+import androidx.window.demo.common.util.PictureInPictureUtil
+import androidx.window.demo.databinding.ActivitySplitPipActivityLayoutBinding
import androidx.window.embedding.ActivityFilter
import androidx.window.embedding.EmbeddingRule
import androidx.window.embedding.RuleController
+import androidx.window.embedding.SplitAttributes
import androidx.window.embedding.SplitController
import androidx.window.embedding.SplitInfo
import androidx.window.embedding.SplitPairFilter
import androidx.window.embedding.SplitPairRule
import androidx.window.embedding.SplitPlaceholderRule
-import androidx.window.embedding.SplitRule
-import androidx.window.sample.R
-import androidx.window.sample.databinding.ActivitySplitPipActivityLayoutBinding
-import androidx.window.sample.util.PictureInPictureUtil.setPictureInPictureParams
-import androidx.window.sample.util.PictureInPictureUtil.startPictureInPicture
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.ADJACENT
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.ALWAYS
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.NEVER
/**
* Sample showcase of split activity rules with picture-in-picture. Allows the user to select some
@@ -134,7 +136,7 @@
return
}
R.id.enter_pip_button -> {
- startPictureInPicture(this, autoEnterPip)
+ PictureInPictureUtil.startPictureInPicture(this, autoEnterPip)
}
}
}
@@ -159,14 +161,14 @@
}
}
}
- setPictureInPictureParams(this, autoEnterPip)
+ PictureInPictureUtil.setPictureInPictureParams(this, autoEnterPip)
}
/** Enters PiP if enterPipOnUserLeave checkbox is checked. */
override fun onUserLeaveHint() {
super.onUserLeaveHint()
if (enterPipOnUserLeave) {
- startPictureInPicture(this, autoEnterPip)
+ PictureInPictureUtil.startPictureInPicture(this, autoEnterPip)
}
}
@@ -182,10 +184,10 @@
viewBinding.splitMainCheckBox.isChecked = true
viewBinding.finishPrimaryWithSecondaryCheckBox.isEnabled = true
viewBinding.finishPrimaryWithSecondaryCheckBox.isChecked =
- splitRule.finishPrimaryWithSecondary == SplitRule.FINISH_ALWAYS
+ splitRule.finishPrimaryWithSecondary == ALWAYS
viewBinding.finishSecondaryWithPrimaryCheckBox.isEnabled = true
viewBinding.finishSecondaryWithPrimaryCheckBox.isChecked =
- splitRule.finishSecondaryWithPrimary == SplitRule.FINISH_ALWAYS
+ splitRule.finishSecondaryWithPrimary == ALWAYS
} else {
viewBinding.splitMainCheckBox.isChecked = false
viewBinding.finishPrimaryWithSecondaryCheckBox.isEnabled = false
@@ -236,7 +238,9 @@
/** Updates the split rules based on the current selection on checkboxes. */
private fun updateSplitRules() {
ruleController.clearRules()
-
+ val defaultSplitAttributes = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(splitRatio))
+ .build()
if (viewBinding.splitMainCheckBox.isChecked) {
val pairFilters = HashSet<SplitPairFilter>()
pairFilters.add(SplitPairFilter(componentNameA, componentNameB, null))
@@ -245,13 +249,14 @@
val finishBWithA = viewBinding.finishSecondaryWithPrimaryCheckBox.isChecked
val rule = SplitPairRule.Builder(pairFilters)
.setMinWidthDp(0)
+ .setMinHeightDp(0)
.setMinSmallestWidthDp(0)
.setFinishPrimaryWithSecondary(
- if (finishAWithB) SplitRule.FINISH_ALWAYS else SplitRule.FINISH_NEVER)
+ if (finishAWithB) ALWAYS else NEVER)
.setFinishSecondaryWithPrimary(
- if (finishBWithA) SplitRule.FINISH_ALWAYS else SplitRule.FINISH_NEVER)
+ if (finishBWithA) ALWAYS else NEVER)
.setClearTop(true)
- .setSplitRatio(splitRatio)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
.build()
ruleController.addRule(rule)
}
@@ -263,10 +268,11 @@
val isSticky = viewBinding.useStickyPlaceHolderCheckBox.isChecked
val rule = SplitPlaceholderRule.Builder(activityFilters, intent)
.setMinWidthDp(0)
+ .setMinHeightDp(0)
.setMinSmallestWidthDp(0)
.setSticky(isSticky)
- .setFinishPrimaryWithPlaceholder(SplitRule.FINISH_ADJACENT)
- .setSplitRatio(splitRatio)
+ .setFinishPrimaryWithPlaceholder(ADJACENT)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
.build()
ruleController.addRule(rule)
}
@@ -291,7 +297,10 @@
override fun accept(newSplitInfos: List<SplitInfo>) {
var isInSplit = false
for (info in newSplitInfos) {
- if (info.contains(this@SplitPipActivityBase) && info.splitRatio > 0) {
+ if (info.contains(this@SplitPipActivityBase) &&
+ info.splitAttributes.splitType !is
+ SplitAttributes.SplitType.ExpandContainersSplitType
+ ) {
isInSplit = true
break
}
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityNoPip.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityNoPip.kt
similarity index 95%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityNoPip.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityNoPip.kt
index 3cfc28c..0af97fb 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityNoPip.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityNoPip.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
import android.os.Bundle
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityPlaceholder.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityPlaceholder.kt
similarity index 93%
rename from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityPlaceholder.kt
rename to window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityPlaceholder.kt
index 7c8639b..5989917 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityPlaceholder.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitPipActivityPlaceholder.kt
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.demo.embedding
open class SplitPipActivityPlaceholder : SplitActivityPlaceholder()
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/drawable/border.xml b/window/window-demos/demo/src/main/res/drawable/border.xml
similarity index 100%
rename from window/window-samples/src/main/res/drawable/border.xml
rename to window/window-demos/demo/src/main/res/drawable/border.xml
diff --git a/window/window-samples/src/main/res/drawable/ic_android_green_320dp.xml b/window/window-demos/demo/src/main/res/drawable/ic_android_green_320dp.xml
similarity index 100%
rename from window/window-samples/src/main/res/drawable/ic_android_green_320dp.xml
rename to window/window-demos/demo/src/main/res/drawable/ic_android_green_320dp.xml
diff --git a/window/window-samples/src/main/res/layout/activity_display_features_no_config_change.xml b/window/window-demos/demo/src/main/res/layout/activity_display_features_no_config_change.xml
similarity index 92%
copy from window/window-samples/src/main/res/layout/activity_display_features_no_config_change.xml
copy to window/window-demos/demo/src/main/res/layout/activity_display_features_no_config_change.xml
index 46ade36..3a86e95 100644
--- a/window/window-samples/src/main/res/layout/activity_display_features_no_config_change.xml
+++ b/window/window-demos/demo/src/main/res/layout/activity_display_features_no_config_change.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2021 The Android Open Source Project
+ 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.
@@ -22,7 +22,7 @@
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context="androidx.window.sample.DisplayFeaturesNoConfigChangeActivity">
+ tools:context="androidx.window.demo.DisplayFeaturesActivity">
<FrameLayout
android:id="@+id/feature_container_layout"
@@ -38,7 +38,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/current_state"
- android:textAppearance="@style/TextAppearance.AppCompat.Large"
+ android:textAppearance="@style/TextAppearance.AppCompat"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
@@ -87,7 +87,7 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/current_state"
+ app:layout_constraintTop_toBottomOf="@id/current_state"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/layout/activity_foldin.xml b/window/window-demos/demo/src/main/res/layout/activity_foldin.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/activity_foldin.xml
rename to window/window-demos/demo/src/main/res/layout/activity_foldin.xml
diff --git a/window/window-samples/src/main/res/layout/activity_ime.xml b/window/window-demos/demo/src/main/res/layout/activity_ime.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/activity_ime.xml
rename to window/window-demos/demo/src/main/res/layout/activity_ime.xml
diff --git a/window/window-samples/src/main/res/layout/activity_organized_test_layout.xml b/window/window-demos/demo/src/main/res/layout/activity_organized_test_layout.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/activity_organized_test_layout.xml
rename to window/window-demos/demo/src/main/res/layout/activity_organized_test_layout.xml
diff --git a/window/window-demos/demo/src/main/res/layout/activity_rear_display.xml b/window/window-demos/demo/src/main/res/layout/activity_rear_display.xml
new file mode 100644
index 0000000..43bea60
--- /dev/null
+++ b/window/window-demos/demo/src/main/res/layout/activity_rear_display.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/rearStatusRecyclerView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <Button
+ android:id="@+id/rear_display_button"
+ android:text="Enable RearDisplay"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAlignment="center"
+ android:layout_marginBottom="32dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/window/window-demos/demo/src/main/res/layout/activity_split_activity_layout.xml b/window/window-demos/demo/src/main/res/layout/activity_split_activity_layout.xml
new file mode 100644
index 0000000..0bbcddf
--- /dev/null
+++ b/window/window-demos/demo/src/main/res/layout/activity_split_activity_layout.xml
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020 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.
+ -->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root_split_activity_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="10dp">
+ <TextView
+ android:id="@+id/activity_embedded_status_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Activity is embedded" />
+
+ <CheckBox
+ android:id="@+id/splitMainCheckBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Split Main with other activities" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+
+ <Button
+ android:id="@+id/launch_b"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="Launch B" />
+
+ <CheckBox
+ android:id="@+id/usePlaceholderCheckBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Use a placeholder for B" />
+
+ <CheckBox
+ android:id="@+id/useStickyPlaceholderCheckBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Placeholder is sticky" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+
+ <Button
+ android:id="@+id/launch_b_and_C"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="Launch B and C" />
+
+ <CheckBox
+ android:id="@+id/splitBCCheckBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Split B with C" />
+
+ <CheckBox
+ android:id="@+id/finishBCCheckBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Finish B and C together"
+ android:enabled="false" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+
+ <Button
+ android:id="@+id/launch_e"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="Launch E" />
+
+ <CheckBox
+ android:id="@+id/fullscreenECheckBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Always launch E in fullscreen" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+
+ <Button
+ android:id="@+id/launch_f"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="Launch f" />
+
+ <Button
+ android:id="@+id/launch_f_pending_intent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="Launch F via Pending Intent" />
+
+ <CheckBox
+ android:id="@+id/splitWithFCheckBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Split everything with F" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:text="Second app (UID)"
+ />
+
+ <Button
+ android:id="@+id/launch_uid2_trusted"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="Launch with known certificate" />
+
+ <Button
+ android:id="@+id/launch_uid2_untrusted"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="Launch in untrusted mode" />
+
+ <Button
+ android:id="@+id/launch_uid2_untrusted_display_features"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="Launch display features" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+
+ <Button
+ android:id="@+id/launch_expanded_dialog_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:text="Launch Expanded Dialog" />
+ </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/layout/activity_split_activity_list_detail_layout.xml b/window/window-demos/demo/src/main/res/layout/activity_split_activity_list_detail_layout.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/activity_split_activity_list_detail_layout.xml
rename to window/window-demos/demo/src/main/res/layout/activity_split_activity_list_detail_layout.xml
diff --git a/window/window-samples/src/main/res/layout/activity_split_activity_list_layout.xml b/window/window-demos/demo/src/main/res/layout/activity_split_activity_list_layout.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/activity_split_activity_list_layout.xml
rename to window/window-demos/demo/src/main/res/layout/activity_split_activity_list_layout.xml
diff --git a/window/window-samples/src/main/res/layout/activity_split_activity_placeholder_layout.xml b/window/window-demos/demo/src/main/res/layout/activity_split_activity_placeholder_layout.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/activity_split_activity_placeholder_layout.xml
rename to window/window-demos/demo/src/main/res/layout/activity_split_activity_placeholder_layout.xml
diff --git a/window/window-demos/demo/src/main/res/layout/activity_split_device_state_layout.xml b/window/window-demos/demo/src/main/res/layout/activity_split_device_state_layout.xml
new file mode 100644
index 0000000..eb67379
--- /dev/null
+++ b/window/window-demos/demo/src/main/res/layout/activity_split_device_state_layout.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+ -->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root_split_activity_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="10dp">
+
+ <TextView
+ android:id="@+id/activity_pair_split_attributes_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/current_split_attributes"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+
+ <TextView
+ android:id="@+id/choose_layout_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Choose the configuration to update the layout:" />
+
+ <RadioGroup
+ android:id="@+id/split_attributes_options_radio_group"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <RadioButton
+ android:id="@+id/use_default_split_attributes"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Use the default split attributes"
+ android:checked="true"/>
+
+ <!-- The fullscreen option group -->
+ <View
+ android:id="@+id/show_fullscreen_in_portrait_divider_top"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA"
+ android:visibility="gone"/>
+ <RadioButton
+ android:id="@+id/show_fullscreen_in_portrait_radio_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Show fullscreen in portrait"
+ android:checked="true"/>
+ <CheckBox
+ android:id="@+id/show_horizontal_layout_in_tabletop_check_box"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Also show horizontal layout in tabletop mode"
+ android:visibility="gone"/>
+ <View
+ android:id="@+id/show_fullscreen_in_portrait_divider_bottom"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA"
+ android:visibility="gone"/>
+ <!-- End of the fullscreen option group -->
+
+ <RadioButton
+ android:id="@+id/show_horizontal_layout_in_tabletop_radio_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Show horizontal layout in tabletop mode"/>
+
+ <!-- The different layout option group -->
+ <View
+ android:id="@+id/show_different_layout_with_size_divider_top"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA"
+ android:visibility="gone"/>
+ <RadioButton
+ android:id="@+id/show_different_layout_with_size_radio_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Show different layout with size"/>
+ <CheckBox
+ android:id="@+id/show_fullscreen_in_book_mode_check_box"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Also show fullscreen in book mode"
+ android:visibility="gone"/>
+ <View
+ android:id="@+id/show_different_layout_with_size_divider_bottom"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA"
+ android:visibility="gone"/>
+ <!-- End of the different layout option group -->
+
+ <RadioButton
+ android:id="@+id/split_by_hinge_when_separating_radio_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Show layout that follows the hinge when it is separated by hinge"/>
+ <CheckBox
+ android:id="@+id/swap_primary_secondary_position_check_box"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Swap the position of primary and secondary container" />
+ </RadioGroup>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+
+ <!-- Dropdown for animation background color -->
+
+ <TextView
+ android:id="@+id/animation_background_color_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/current_animation_background_color"/>
+
+ <Spinner
+ android:id="@+id/animation_background_color_dropdown"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:spinnerMode="dropdown" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+
+ <Button
+ android:id="@+id/launch_activity_to_side"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_centerHorizontal="true"
+ android:text="Launch activity to side"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="#AAAAAA" />
+ <TextView
+ android:id="@+id/error_message_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/layout/activity_split_layout.xml b/window/window-demos/demo/src/main/res/layout/activity_split_layout.xml
similarity index 94%
rename from window/window-samples/src/main/res/layout/activity_split_layout.xml
rename to window/window-demos/demo/src/main/res/layout/activity_split_layout.xml
index 6950bf3..661df38 100644
--- a/window/window-samples/src/main/res/layout/activity_split_layout.xml
+++ b/window/window-demos/demo/src/main/res/layout/activity_split_layout.xml
@@ -24,7 +24,7 @@
android:layout_height="match_parent"
tools:context="SplitLayoutActivity">
- <androidx.window.sample.SplitLayout
+ <androidx.window.demo.SplitLayout
android:id="@+id/split_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -37,5 +37,5 @@
<include
android:id="@id/end_layout"
layout="@layout/split_layout_control" />
- </androidx.window.sample.SplitLayout>
+ </androidx.window.demo.SplitLayout>
</LinearLayout>
diff --git a/window/window-samples/src/main/res/layout/activity_split_pip_activity_layout.xml b/window/window-demos/demo/src/main/res/layout/activity_split_pip_activity_layout.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/activity_split_pip_activity_layout.xml
rename to window/window-demos/demo/src/main/res/layout/activity_split_pip_activity_layout.xml
diff --git a/window/window-samples/src/main/res/layout/activity_window_demos.xml b/window/window-demos/demo/src/main/res/layout/activity_window_demos.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/activity_window_demos.xml
rename to window/window-demos/demo/src/main/res/layout/activity_window_demos.xml
diff --git a/window/window-samples/src/main/res/layout/activity_window_metrics.xml b/window/window-demos/demo/src/main/res/layout/activity_window_metrics.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/activity_window_metrics.xml
rename to window/window-demos/demo/src/main/res/layout/activity_window_metrics.xml
diff --git a/window/window-samples/src/main/res/layout/presentation_second_display.xml b/window/window-demos/demo/src/main/res/layout/presentation_second_display.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/presentation_second_display.xml
rename to window/window-demos/demo/src/main/res/layout/presentation_second_display.xml
diff --git a/window/window-samples/src/main/res/layout/split_layout_content.xml b/window/window-demos/demo/src/main/res/layout/split_layout_content.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/split_layout_content.xml
rename to window/window-demos/demo/src/main/res/layout/split_layout_content.xml
diff --git a/window/window-samples/src/main/res/layout/split_layout_control.xml b/window/window-demos/demo/src/main/res/layout/split_layout_control.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/split_layout_control.xml
rename to window/window-demos/demo/src/main/res/layout/split_layout_control.xml
diff --git a/window/window-demos/demo/src/main/res/layout/test_ime.xml b/window/window-demos/demo/src/main/res/layout/test_ime.xml
new file mode 100644
index 0000000..07f5691
--- /dev/null
+++ b/window/window-demos/demo/src/main/res/layout/test_ime.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/imeBackground">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="140dp"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/button_clear"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/test_ime_button_clear"/>
+
+ <Button
+ android:id="@+id/button_close"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/test_ime_button_close"/>
+
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/layout/view_holder_demo_item.xml b/window/window-demos/demo/src/main/res/layout/view_holder_demo_item.xml
similarity index 100%
rename from window/window-samples/src/main/res/layout/view_holder_demo_item.xml
rename to window/window-demos/demo/src/main/res/layout/view_holder_demo_item.xml
diff --git a/window/window-samples/src/main/res/menu/picture_in_picture_menu.xml b/window/window-demos/demo/src/main/res/menu/picture_in_picture_menu.xml
similarity index 100%
rename from window/window-samples/src/main/res/menu/picture_in_picture_menu.xml
rename to window/window-demos/demo/src/main/res/menu/picture_in_picture_menu.xml
diff --git a/window/window-samples/src/main/res/values/attrs.xml b/window/window-demos/demo/src/main/res/values/attrs.xml
similarity index 100%
rename from window/window-samples/src/main/res/values/attrs.xml
rename to window/window-demos/demo/src/main/res/values/attrs.xml
diff --git a/window/window-samples/src/main/res/values/colors.xml b/window/window-demos/demo/src/main/res/values/colors.xml
similarity index 95%
rename from window/window-samples/src/main/res/values/colors.xml
rename to window/window-demos/demo/src/main/res/values/colors.xml
index 41a72b2..95a6cfe 100644
--- a/window/window-samples/src/main/res/values/colors.xml
+++ b/window/window-demos/demo/src/main/res/values/colors.xml
@@ -24,4 +24,6 @@
<color name="colorSplitContentBackground">#3B6BDB4C</color>
<color name="colorSplitControlsBackground">#475ABFF3</color>
+
+ <color name="imeBackground">#EEEEEE</color>
</resources>
diff --git a/window/window-samples/src/main/res/values/strings.xml b/window/window-demos/demo/src/main/res/values/strings.xml
similarity index 83%
rename from window/window-samples/src/main/res/values/strings.xml
rename to window/window-demos/demo/src/main/res/values/strings.xml
index 45e3de2..1a86d90 100644
--- a/window/window-samples/src/main/res/values/strings.xml
+++ b/window/window-demos/demo/src/main/res/values/strings.xml
@@ -16,9 +16,7 @@
<resources>
<string name="app_name">WindowSamples</string>
- <string name="current_state">Current state</string>
<string name="deviceState">Device state</string>
- <string name="window_layout">Window layout</string>
<string name="fold">Fold</string>
<string name="legend">Legend:</string>
<string name="content_title">Content title</string>
@@ -43,15 +41,14 @@
<string name="show_all_display_features_no_config_change_description">Show all display features of the device on the screen and do not handle config changes. The activity is recreated instead on rotation or resize</string>
<string name="split_layout_demo_description">Demo of a layout that splits the content to sides of a fold or a hinge. If not present or minimal size requirements are not meant, it behave like a FrameLayout.</string>
<string name="presentation_demo_description">Demo of using Presentation API to show content on secondary display.</string>
- <string name="screens_are_separated">"Screens are separated"</string>
- <string name="screens_are_not_separated">"Screen is not separated"</string>
- <string name="screen_is_horizontal">"Hinge is horizontal"</string>
- <string name="screen_is_vertical">"Hinge is vertical"</string>
- <string name="occlusion_is_full">Occlusion is full</string>
- <string name="occlusion_is_none">Occlusion is none</string>
<string name="window_metrics">Window metrics</string>
<string name="window_metrics_description">Demo of using WindowMetrics API with activity handling rotations.</string>
+ <string name="rear_display">Rear Display Mode</string>
+ <string name="rear_display_description">Demo of observing to WindowAreaStatus and enabling/disabling RearDisplay mode</string>
+ <string name="current_split_attributes">Current SplitAttributes:</string>>
+ <string name="current_animation_background_color">Current Animation Background Color:</string>>
<string name="test_ime">Test IME</string>
+ <string name="test_ime_button_clear">Clear Logs</string>
<string name="test_ime_button_close">Close Test IME</string>
<string name="window_metrics_ime_hint">Tap to open IME</string>
<string name="ime">IME</string>
@@ -59,4 +56,6 @@
<string name="ime_demo_reminder">Reminder: To use the Test IME bundled with this application, remember to enable it in System Settings.</string>
<string name="ime_button_settings">System IME Settings</string>
<string name="ime_button_switch_default">Switch default IME</string>
+ <string name="install_samples_2">Install window-demos:demo-second-app to launch activities from a different UID.</string>
+ <string name="toast_split_not_support">Please enable PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED and ensure device supports splits.</string>
</resources>
diff --git a/window/window-samples/src/main/res/values/styles.xml b/window/window-demos/demo/src/main/res/values/styles.xml
similarity index 78%
copy from window/window-samples/src/main/res/values/styles.xml
copy to window/window-demos/demo/src/main/res/values/styles.xml
index eaa9ab2..5586114 100644
--- a/window/window-samples/src/main/res/values/styles.xml
+++ b/window/window-demos/demo/src/main/res/values/styles.xml
@@ -24,4 +24,9 @@
<item name="colorAccent">@color/colorAccent</item>
</style>
+ <!-- Theme to show the expanded dialog Activity as transparent. -->
+ <style name="ExpandedDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
+ <item name="windowNoTitle">true</item>
+ <item name="android:windowActionBar">false</item>
+ </style>
</resources>
diff --git a/window/window-samples/src/main/res/xml/main_split_config.xml b/window/window-demos/demo/src/main/res/xml/main_split_config.xml
similarity index 73%
rename from window/window-samples/src/main/res/xml/main_split_config.xml
rename to window/window-demos/demo/src/main/res/xml/main_split_config.xml
index 5b93a1c..e4b150d 100644
--- a/window/window-samples/src/main/res/xml/main_split_config.xml
+++ b/window/window-demos/demo/src/main/res/xml/main_split_config.xml
@@ -20,14 +20,14 @@
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="adjacent">
<SplitPairFilter
- window:primaryActivityName="androidx.window.sample.embedding.SplitActivityList"
- window:secondaryActivityName="androidx.window.sample.embedding.SplitActivityDetail"/>
+ window:primaryActivityName="androidx.window.demo.embedding.SplitActivityList"
+ window:secondaryActivityName="androidx.window.demo.embedding.SplitActivityDetail"/>
</SplitPairRule>
<SplitPlaceholderRule
- window:placeholderActivityName="androidx.window.sample.embedding.SplitActivityListPlaceholder"
+ window:placeholderActivityName="androidx.window.demo.embedding.SplitActivityListPlaceholder"
window:stickyPlaceholder="true"
window:finishPrimaryWithSecondary="adjacent">
<ActivityFilter
- window:activityName="androidx.window.sample.embedding.SplitActivityList"/>
+ window:activityName="androidx.window.demo.embedding.SplitActivityList"/>
</SplitPlaceholderRule>
</resources>
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/xml/method.xml b/window/window-demos/demo/src/main/res/xml/method.xml
similarity index 100%
rename from window/window-samples/src/main/res/xml/method.xml
rename to window/window-demos/demo/src/main/res/xml/method.xml
diff --git a/window/window-java/api/current.txt b/window/window-java/api/current.txt
index 709904b..39c35ac 100644
--- a/window/window-java/api/current.txt
+++ b/window/window-java/api/current.txt
@@ -4,6 +4,7 @@
public final class WindowInfoTrackerCallbackAdapter implements androidx.window.layout.WindowInfoTracker {
ctor public WindowInfoTrackerCallbackAdapter(androidx.window.layout.WindowInfoTracker tracker);
method public void addWindowLayoutInfoListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+ method public void addWindowLayoutInfoListener(@UiContext android.content.Context context, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
method public void removeWindowLayoutInfoListener(androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
}
diff --git a/window/window-java/api/public_plus_experimental_current.txt b/window/window-java/api/public_plus_experimental_current.txt
index 709904b..39c35ac 100644
--- a/window/window-java/api/public_plus_experimental_current.txt
+++ b/window/window-java/api/public_plus_experimental_current.txt
@@ -4,6 +4,7 @@
public final class WindowInfoTrackerCallbackAdapter implements androidx.window.layout.WindowInfoTracker {
ctor public WindowInfoTrackerCallbackAdapter(androidx.window.layout.WindowInfoTracker tracker);
method public void addWindowLayoutInfoListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+ method public void addWindowLayoutInfoListener(@UiContext android.content.Context context, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
method public void removeWindowLayoutInfoListener(androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
}
diff --git a/window/window-java/api/restricted_current.txt b/window/window-java/api/restricted_current.txt
index 709904b..39c35ac 100644
--- a/window/window-java/api/restricted_current.txt
+++ b/window/window-java/api/restricted_current.txt
@@ -4,6 +4,7 @@
public final class WindowInfoTrackerCallbackAdapter implements androidx.window.layout.WindowInfoTracker {
ctor public WindowInfoTrackerCallbackAdapter(androidx.window.layout.WindowInfoTracker tracker);
method public void addWindowLayoutInfoListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
+ method public void addWindowLayoutInfoListener(@UiContext android.content.Context context, java.util.concurrent.Executor executor, androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
method public void removeWindowLayoutInfoListener(androidx.core.util.Consumer<androidx.window.layout.WindowLayoutInfo> consumer);
}
diff --git a/window/window-java/src/androidTest/java/androidx/window/java/layout/WindowInfoTrackerCallbackAdapterTest.kt b/window/window-java/src/androidTest/java/androidx/window/java/layout/WindowInfoTrackerCallbackAdapterTest.kt
index 8c4fc81..74b3d87 100644
--- a/window/window-java/src/androidTest/java/androidx/window/java/layout/WindowInfoTrackerCallbackAdapterTest.kt
+++ b/window/window-java/src/androidTest/java/androidx/window/java/layout/WindowInfoTrackerCallbackAdapterTest.kt
@@ -17,6 +17,7 @@
package androidx.window.java.layout
import android.app.Activity
+import android.content.Context
import androidx.window.java.TestConsumer
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
@@ -52,6 +53,21 @@
}
@Test
+ public fun testRegisterListenerForUiContext() {
+ val context = mock<Context>()
+ val feature = mock<FoldingFeature>()
+ val expected = WindowLayoutInfo(listOf(feature))
+ val mockTracker = mock<WindowInfoTracker>()
+ whenever(mockTracker.windowLayoutInfo(context)).thenReturn(flowOf(expected))
+ val unitUnderTest = WindowInfoTrackerCallbackAdapter(mockTracker)
+ val testConsumer = TestConsumer<WindowLayoutInfo>()
+
+ unitUnderTest.addWindowLayoutInfoListener(context, Runnable::run, testConsumer)
+
+ testConsumer.assertValue(expected)
+ }
+
+ @Test
public fun testWindowLayoutInfo_registerMultipleIsNoOp() {
val activity = mock<Activity>()
val feature = mock<FoldingFeature>()
@@ -86,4 +102,24 @@
assertTrue(accepted)
testConsumer.assertEmpty()
}
+
+ @Test
+ public fun testWindowLayoutInfo_unregisterForUiContext() {
+ val context = mock<Context>()
+ val feature = mock<FoldingFeature>()
+ val info = WindowLayoutInfo(listOf(feature))
+ val mockTracker = mock<WindowInfoTracker>()
+ val channel = Channel<WindowLayoutInfo>()
+ whenever(mockTracker.windowLayoutInfo(context)).thenReturn(channel.receiveAsFlow())
+ val unitUnderTest = WindowInfoTrackerCallbackAdapter(mockTracker)
+ val testConsumer = TestConsumer<WindowLayoutInfo>()
+
+ unitUnderTest.addWindowLayoutInfoListener(context, Runnable::run, testConsumer)
+ unitUnderTest.addWindowLayoutInfoListener(context, Runnable::run, mock())
+ unitUnderTest.removeWindowLayoutInfoListener(testConsumer)
+ val accepted = channel.trySend(info).isSuccess
+
+ assertTrue(accepted)
+ testConsumer.assertEmpty()
+ }
}
diff --git a/window/window-java/src/main/java/androidx/window/java/layout/WindowInfoTrackerCallbackAdapter.kt b/window/window-java/src/main/java/androidx/window/java/layout/WindowInfoTrackerCallbackAdapter.kt
index 5d716c6..5966d28 100644
--- a/window/window-java/src/main/java/androidx/window/java/layout/WindowInfoTrackerCallbackAdapter.kt
+++ b/window/window-java/src/main/java/androidx/window/java/layout/WindowInfoTrackerCallbackAdapter.kt
@@ -17,7 +17,10 @@
package androidx.window.java.layout
import android.app.Activity
+import android.content.Context
+import androidx.annotation.UiContext
import androidx.core.util.Consumer
+import android.inputmethodservice.InputMethodService
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
import kotlinx.coroutines.CoroutineScope
@@ -45,8 +48,11 @@
private val consumerToJobMap = mutableMapOf<Consumer<*>, Job>()
/**
- * Register a listener to consume [WindowLayoutInfo] values. If the same consumer is
- * registered twice then this method is a no-op.
+ * Registers a listener to consume [WindowLayoutInfo] values of the [Activity] window. If the
+ * same consumer is registered twice then this method is a no-op.
+ * @param activity an [Activity] that hosts a [Window].
+ * @param executor that the consumer will invoke on.
+ * @param consumer for [WindowLayoutInfo] values.
* @see WindowInfoTracker.windowLayoutInfo
*/
fun addWindowLayoutInfoListener(
@@ -58,6 +64,23 @@
}
/**
+ * Registers a [UiContext] listener to consume [WindowLayoutInfo] values. If the same consumer
+ * is registered twice then this method is a no-op.
+ * @param context a [UiContext] such as an [Activity], created with
+ * [Context#createWindowContext] or is a [InputMethodService].
+ * @param executor that the consumer will invoke on.
+ * @param consumer for [WindowLayoutInfo] values.
+ * @see WindowInfoTracker.windowLayoutInfo
+ */
+ fun addWindowLayoutInfoListener(
+ @UiContext context: Context,
+ executor: Executor,
+ consumer: Consumer<WindowLayoutInfo>
+ ) {
+ addListener(executor, consumer, tracker.windowLayoutInfo(context))
+ }
+
+ /**
* Remove a listener to stop consuming [WindowLayoutInfo] values. If the listener has already
* been removed then this is a no-op.
* @see WindowInfoTracker.windowLayoutInfo
diff --git a/window/window-rxjava2/api/api_lint.ignore b/window/window-rxjava2/api/api_lint.ignore
new file mode 100644
index 0000000..636cfcc
--- /dev/null
+++ b/window/window-rxjava2/api/api_lint.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+ContextFirst: androidx.window.rxjava2.layout.WindowInfoTrackerRx#windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.content.Context) parameter #1:
+ Context is distinct, so it must be the first argument (method `windowLayoutInfoFlowable`)
+ContextFirst: androidx.window.rxjava2.layout.WindowInfoTrackerRx#windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.content.Context) parameter #1:
+ Context is distinct, so it must be the first argument (method `windowLayoutInfoObservable`)
diff --git a/window/window-rxjava2/api/current.txt b/window/window-rxjava2/api/current.txt
index 8135cee..5250696 100644
--- a/window/window-rxjava2/api/current.txt
+++ b/window/window-rxjava2/api/current.txt
@@ -3,7 +3,9 @@
public final class WindowInfoTrackerRx {
method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
}
}
diff --git a/window/window-rxjava2/api/public_plus_experimental_current.txt b/window/window-rxjava2/api/public_plus_experimental_current.txt
index 8135cee..5250696 100644
--- a/window/window-rxjava2/api/public_plus_experimental_current.txt
+++ b/window/window-rxjava2/api/public_plus_experimental_current.txt
@@ -3,7 +3,9 @@
public final class WindowInfoTrackerRx {
method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
}
}
diff --git a/window/window-rxjava2/api/restricted_current.txt b/window/window-rxjava2/api/restricted_current.txt
index 8135cee..5250696 100644
--- a/window/window-rxjava2/api/restricted_current.txt
+++ b/window/window-rxjava2/api/restricted_current.txt
@@ -3,7 +3,9 @@
public final class WindowInfoTrackerRx {
method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
}
}
diff --git a/window/window-rxjava2/build.gradle b/window/window-rxjava2/build.gradle
index 389a75c..dee2b16 100644
--- a/window/window-rxjava2/build.gradle
+++ b/window/window-rxjava2/build.gradle
@@ -37,6 +37,7 @@
api(libs.kotlinCoroutinesRx2)
api(libs.rxjava2)
api(project(":window:window"))
+ implementation 'androidx.annotation:annotation:1.5.0'
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testRunner)
diff --git a/window/window-rxjava2/src/androidTest/java/androidx/window/rxjava2/layout/WindowInfoTrackerRxTest.kt b/window/window-rxjava2/src/androidTest/java/androidx/window/rxjava2/layout/WindowInfoTrackerRxTest.kt
index a22df89..418725d 100644
--- a/window/window-rxjava2/src/androidTest/java/androidx/window/rxjava2/layout/WindowInfoTrackerRxTest.kt
+++ b/window/window-rxjava2/src/androidTest/java/androidx/window/rxjava2/layout/WindowInfoTrackerRxTest.kt
@@ -17,6 +17,7 @@
package androidx.window.rxjava2.layout
import android.app.Activity
+import android.content.Context
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
@@ -28,10 +29,10 @@
/**
* Tests for the RxJava 2 adapters.
*/
-public class WindowInfoTrackerRxTest {
+class WindowInfoTrackerRxTest {
@Test
- public fun testWindowLayoutInfoObservable() {
+ fun testWindowLayoutInfoObservable() {
val activity = mock<Activity>()
val feature = mock<FoldingFeature>()
val expected = WindowLayoutInfo(listOf(feature))
@@ -44,7 +45,7 @@
}
@Test
- public fun testWindowLayoutInfoFlowable() {
+ fun testWindowLayoutInfoFlowable() {
val activity = mock<Activity>()
val feature = mock<FoldingFeature>()
val expected = WindowLayoutInfo(listOf(feature))
@@ -55,4 +56,29 @@
testSubscriber.assertValue(expected)
}
+ @Test
+ fun testWindowLayoutInfoObservable_context() {
+ val activity = mock<Context>()
+ val feature = mock<FoldingFeature>()
+ val expected = WindowLayoutInfo(listOf(feature))
+ val mockTracker = mock<WindowInfoTracker>()
+ whenever(mockTracker.windowLayoutInfo(activity)).thenReturn(flowOf(expected))
+
+ val testSubscriber = mockTracker.windowLayoutInfoObservable(activity).test()
+
+ testSubscriber.assertValue(expected)
+ }
+
+ @Test
+ fun testWindowLayoutInfoFlowable_context() {
+ val activity = mock<Context>()
+ val feature = mock<FoldingFeature>()
+ val expected = WindowLayoutInfo(listOf(feature))
+ val mockTracker = mock<WindowInfoTracker>()
+ whenever(mockTracker.windowLayoutInfo(activity)).thenReturn(flowOf(expected))
+
+ val testSubscriber = mockTracker.windowLayoutInfoFlowable(activity).test()
+
+ testSubscriber.assertValue(expected)
+ }
}
diff --git a/window/window-rxjava2/src/main/java/androidx/window/rxjava2/layout/WindowInfoTrackerRx.kt b/window/window-rxjava2/src/main/java/androidx/window/rxjava2/layout/WindowInfoTrackerRx.kt
index 2a82d8c..d41cc80 100644
--- a/window/window-rxjava2/src/main/java/androidx/window/rxjava2/layout/WindowInfoTrackerRx.kt
+++ b/window/window-rxjava2/src/main/java/androidx/window/rxjava2/layout/WindowInfoTrackerRx.kt
@@ -18,6 +18,8 @@
package androidx.window.rxjava2.layout
import android.app.Activity
+import android.content.Context
+import androidx.annotation.UiContext
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
import io.reactivex.Flowable
@@ -29,7 +31,7 @@
* Return an [Observable] stream of [WindowLayoutInfo].
* @see WindowInfoTracker.windowLayoutInfo
*/
-public fun WindowInfoTracker.windowLayoutInfoObservable(
+fun WindowInfoTracker.windowLayoutInfoObservable(
activity: Activity
): Observable<WindowLayoutInfo> {
return windowLayoutInfo(activity).asObservable()
@@ -39,8 +41,28 @@
* Return a [Flowable] stream of [WindowLayoutInfo].
* @see WindowInfoTracker.windowLayoutInfo
*/
-public fun WindowInfoTracker.windowLayoutInfoFlowable(
+fun WindowInfoTracker.windowLayoutInfoFlowable(
activity: Activity
): Flowable<WindowLayoutInfo> {
return windowLayoutInfo(activity).asFlowable()
}
+
+/**
+ * Return an [Observable] stream of [WindowLayoutInfo].
+ * @see WindowInfoTracker.windowLayoutInfo
+ */
+fun WindowInfoTracker.windowLayoutInfoObservable(
+ @UiContext context: Context
+): Observable<WindowLayoutInfo> {
+ return windowLayoutInfo(context).asObservable()
+}
+
+/**
+ * Return a [Flowable] stream of [WindowLayoutInfo].
+ * @see WindowInfoTracker.windowLayoutInfo
+ */
+fun WindowInfoTracker.windowLayoutInfoFlowable(
+ @UiContext context: Context
+): Flowable<WindowLayoutInfo> {
+ return windowLayoutInfo(context).asFlowable()
+}
diff --git a/window/window-rxjava3/api/api_lint.ignore b/window/window-rxjava3/api/api_lint.ignore
new file mode 100644
index 0000000..8c55c33
--- /dev/null
+++ b/window/window-rxjava3/api/api_lint.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+ContextFirst: androidx.window.rxjava3.layout.WindowInfoTrackerRx#windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.content.Context) parameter #1:
+ Context is distinct, so it must be the first argument (method `windowLayoutInfoFlowable`)
+ContextFirst: androidx.window.rxjava3.layout.WindowInfoTrackerRx#windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.content.Context) parameter #1:
+ Context is distinct, so it must be the first argument (method `windowLayoutInfoObservable`)
diff --git a/window/window-rxjava3/api/current.txt b/window/window-rxjava3/api/current.txt
index 5700dd3..23510cc 100644
--- a/window/window-rxjava3/api/current.txt
+++ b/window/window-rxjava3/api/current.txt
@@ -3,7 +3,9 @@
public final class WindowInfoTrackerRx {
method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
}
}
diff --git a/window/window-rxjava3/api/public_plus_experimental_current.txt b/window/window-rxjava3/api/public_plus_experimental_current.txt
index 5700dd3..23510cc 100644
--- a/window/window-rxjava3/api/public_plus_experimental_current.txt
+++ b/window/window-rxjava3/api/public_plus_experimental_current.txt
@@ -3,7 +3,9 @@
public final class WindowInfoTrackerRx {
method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
}
}
diff --git a/window/window-rxjava3/api/restricted_current.txt b/window/window-rxjava3/api/restricted_current.txt
index 5700dd3..23510cc 100644
--- a/window/window-rxjava3/api/restricted_current.txt
+++ b/window/window-rxjava3/api/restricted_current.txt
@@ -3,7 +3,9 @@
public final class WindowInfoTrackerRx {
method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Flowable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoFlowable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, android.app.Activity activity);
+ method public static io.reactivex.rxjava3.core.Observable<androidx.window.layout.WindowLayoutInfo> windowLayoutInfoObservable(androidx.window.layout.WindowInfoTracker, @UiContext android.content.Context context);
}
}
diff --git a/window/window-rxjava3/build.gradle b/window/window-rxjava3/build.gradle
index 6874507..32804c4 100644
--- a/window/window-rxjava3/build.gradle
+++ b/window/window-rxjava3/build.gradle
@@ -36,6 +36,7 @@
api(libs.kotlinCoroutinesRx3)
api(libs.rxjava3)
api(project(":window:window"))
+ implementation 'androidx.annotation:annotation:1.5.0'
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testRunner)
diff --git a/window/window-rxjava3/src/androidTest/java/androidx/window/rxjava3/layout/WindowInfoTrackerRxTest.kt b/window/window-rxjava3/src/androidTest/java/androidx/window/rxjava3/layout/WindowInfoTrackerRxTest.kt
index 04c68fc..f4a4c4c 100644
--- a/window/window-rxjava3/src/androidTest/java/androidx/window/rxjava3/layout/WindowInfoTrackerRxTest.kt
+++ b/window/window-rxjava3/src/androidTest/java/androidx/window/rxjava3/layout/WindowInfoTrackerRxTest.kt
@@ -17,6 +17,7 @@
package androidx.window.rxjava3.layout
import android.app.Activity
+import android.content.Context
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
@@ -30,10 +31,10 @@
* [io.reactivex.rxjava3.core.Flowable] and ensure that data is forwarded appropriately.
* @see WindowInfoTracker
*/
-public class WindowInfoTrackerRxTest {
+class WindowInfoTrackerRxTest {
@Test
- public fun testWindowLayoutInfoObservable() {
+ fun testWindowLayoutInfoObservable() {
val activity = mock<Activity>()
val feature = mock<FoldingFeature>()
val expected = WindowLayoutInfo(listOf(feature))
@@ -46,7 +47,7 @@
}
@Test
- public fun testWindowLayoutInfoFlowable() {
+ fun testWindowLayoutInfoFlowable() {
val activity = mock<Activity>()
val feature = mock<FoldingFeature>()
val expected = WindowLayoutInfo(listOf(feature))
@@ -57,4 +58,30 @@
testSubscriber.assertValue(expected)
}
+
+ @Test
+ fun testWindowLayoutInfoObservable_context() {
+ val activity = mock<Context>()
+ val feature = mock<FoldingFeature>()
+ val expected = WindowLayoutInfo(listOf(feature))
+ val mockTracker = mock<WindowInfoTracker>()
+ whenever(mockTracker.windowLayoutInfo(activity)).thenReturn(flowOf(expected))
+
+ val testSubscriber = mockTracker.windowLayoutInfoObservable(activity).test()
+
+ testSubscriber.assertValue(expected)
+ }
+
+ @Test
+ fun testWindowLayoutInfoFlowable_context() {
+ val activity = mock<Context>()
+ val feature = mock<FoldingFeature>()
+ val expected = WindowLayoutInfo(listOf(feature))
+ val mockTracker = mock<WindowInfoTracker>()
+ whenever(mockTracker.windowLayoutInfo(activity)).thenReturn(flowOf(expected))
+
+ val testSubscriber = mockTracker.windowLayoutInfoFlowable(activity).test()
+
+ testSubscriber.assertValue(expected)
+ }
}
diff --git a/window/window-rxjava3/src/main/java/androidx/window/rxjava3/layout/WindowInfoTrackerRx.kt b/window/window-rxjava3/src/main/java/androidx/window/rxjava3/layout/WindowInfoTrackerRx.kt
index 374bbbb..084292b 100644
--- a/window/window-rxjava3/src/main/java/androidx/window/rxjava3/layout/WindowInfoTrackerRx.kt
+++ b/window/window-rxjava3/src/main/java/androidx/window/rxjava3/layout/WindowInfoTrackerRx.kt
@@ -18,6 +18,8 @@
package androidx.window.rxjava3.layout
import android.app.Activity
+import android.content.Context
+import androidx.annotation.UiContext
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
import io.reactivex.rxjava3.core.Flowable
@@ -29,7 +31,7 @@
* Return an [Observable] stream of [WindowLayoutInfo].
* @see WindowInfoTracker.windowLayoutInfo
*/
-public fun WindowInfoTracker.windowLayoutInfoObservable(
+fun WindowInfoTracker.windowLayoutInfoObservable(
activity: Activity
): Observable<WindowLayoutInfo> {
return windowLayoutInfo(activity).asObservable()
@@ -39,8 +41,28 @@
* Return a [Flowable] stream of [WindowLayoutInfo].
* @see WindowInfoTracker.windowLayoutInfo
*/
-public fun WindowInfoTracker.windowLayoutInfoFlowable(
+fun WindowInfoTracker.windowLayoutInfoFlowable(
activity: Activity
): Flowable<WindowLayoutInfo> {
return windowLayoutInfo(activity).asFlowable()
}
+
+/**
+ * Return an [Observable] stream of [WindowLayoutInfo].
+ * @see WindowInfoTracker.windowLayoutInfo
+ */
+fun WindowInfoTracker.windowLayoutInfoObservable(
+ @UiContext context: Context
+): Observable<WindowLayoutInfo> {
+ return windowLayoutInfo(context).asObservable()
+}
+
+/**
+ * Return a [Flowable] stream of [WindowLayoutInfo].
+ * @see WindowInfoTracker.windowLayoutInfo
+ */
+fun WindowInfoTracker.windowLayoutInfoFlowable(
+ @UiContext context: Context
+): Flowable<WindowLayoutInfo> {
+ return windowLayoutInfo(context).asFlowable()
+}
diff --git a/window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesConfigChangeActivity.kt b/window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesConfigChangeActivity.kt
deleted file mode 100644
index 7f07d24..0000000
--- a/window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesConfigChangeActivity.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2020 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.sample
-
-import android.graphics.drawable.ColorDrawable
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import android.widget.FrameLayout
-import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.RecyclerView
-import androidx.window.layout.WindowInfoTracker
-import androidx.window.layout.WindowLayoutInfo
-import androidx.window.sample.infolog.InfoLogAdapter
-import androidx.window.sample.util.PictureInPictureUtil.appendPictureInPictureMenu
-import androidx.window.sample.util.PictureInPictureUtil.handlePictureInPictureMenuItem
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-
-/** Demo activity that shows all display features and current device state on the screen. */
-class DisplayFeaturesConfigChangeActivity : AppCompatActivity() {
-
- private val infoLogAdapter = InfoLogAdapter()
- private val displayFeatureViews = ArrayList<View>()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_display_features_config_change)
- val recyclerView = findViewById<RecyclerView>(R.id.infoLogRecyclerView)
- recyclerView.adapter = infoLogAdapter
-
- lifecycleScope.launch(Dispatchers.Main) {
- // The block passed to repeatOnLifecycle is executed when the lifecycle
- // is at least STARTED and is cancelled when the lifecycle is STOPPED.
- // It automatically restarts the block when the lifecycle is STARTED again.
- lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
- // Safely collect from windowInfoRepo when the lifecycle is STARTED
- // and stops collection when the lifecycle is STOPPED
- WindowInfoTracker.getOrCreate(this@DisplayFeaturesConfigChangeActivity)
- .windowLayoutInfo(this@DisplayFeaturesConfigChangeActivity)
- .collect { newLayoutInfo ->
- // New posture information
- updateStateLog(newLayoutInfo)
- updateCurrentState(newLayoutInfo)
- }
- }
- }
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- appendPictureInPictureMenu(menuInflater, menu)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when {
- handlePictureInPictureMenuItem(this, item) -> true
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- /** Updates the device state and display feature positions. */
- private fun updateCurrentState(windowLayoutInfo: WindowLayoutInfo) {
- // Cleanup previously added feature views
- val rootLayout = findViewById<FrameLayout>(R.id.featureContainerLayout)
- for (featureView in displayFeatureViews) {
- rootLayout.removeView(featureView)
- }
- displayFeatureViews.clear()
-
- // Add views that represent display features
- for (displayFeature in windowLayoutInfo.displayFeatures) {
- val lp = getLayoutParamsForFeatureInFrameLayout(displayFeature, rootLayout)
-
- // Make sure that zero-wide and zero-high features are still shown
- if (lp.width == 0) {
- lp.width = 1
- }
- if (lp.height == 0) {
- lp.height = 1
- }
-
- val featureView = View(this)
- val color = getColor(R.color.colorFeatureFold)
- featureView.foreground = ColorDrawable(color)
-
- rootLayout.addView(featureView, lp)
- featureView.id = View.generateViewId()
-
- displayFeatureViews.add(featureView)
- }
- }
-
- /** Adds the current state to the text log of changes on screen. */
- private fun updateStateLog(info: Any) {
- infoLogAdapter.append(getCurrentTimeString(), info.toString())
- infoLogAdapter.notifyDataSetChanged()
- }
-
- private fun getCurrentTimeString(): String {
- val sdf = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
- val currentDate = sdf.format(Date())
- return currentDate.toString()
- }
-}
diff --git a/window/window-samples/src/main/java/androidx/window/sample/TestIme.kt b/window/window-samples/src/main/java/androidx/window/sample/TestIme.kt
deleted file mode 100644
index d337e11..0000000
--- a/window/window-samples/src/main/java/androidx/window/sample/TestIme.kt
+++ /dev/null
@@ -1,42 +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.sample
-
-import android.inputmethodservice.InputMethodService
-import android.view.View
-import android.view.inputmethod.InputMethodManager
-import android.widget.Button
-
-/**
- * A test IME that currently provides a minimal UI containing a "Close" button. To use this, go to
- * "Settings > System > Languages & Input > On-screen keyboard" and enable "Test IME". Remember you
- * may still need to switch to this IME after the default on-screen keyboard pops up.
- */
-internal class TestIme : InputMethodService() {
-
- override fun onCreateInputView(): View {
- return layoutInflater.inflate(R.layout.test_ime, null).apply {
- findViewById<Button>(R.id.button_close).setOnClickListener {
- requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS)
- }
- }
- }
-
- override fun onEvaluateFullscreenMode(): Boolean {
- return false
- }
-}
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt b/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt
deleted file mode 100644
index 56f41445..0000000
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt
+++ /dev/null
@@ -1,36 +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.sample.embedding
-
-import android.content.Context
-import androidx.startup.Initializer
-import androidx.window.embedding.RuleController
-import androidx.window.sample.R
-
-/**
- * Initializes [RuleController] with a set of statically defined rules.
- */
-class ExampleWindowInitializer : Initializer<RuleController> {
- override fun create(context: Context): RuleController =
- RuleController.getInstance(context).apply {
- setRules(RuleController.parseRules(context, R.xml.main_split_config))
- }
-
- override fun dependencies(): List<Class<out Initializer<*>>> {
- return emptyList()
- }
-}
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/layout/activity_display_features_config_change.xml b/window/window-samples/src/main/res/layout/activity_display_features_config_change.xml
deleted file mode 100644
index 0e877e4..0000000
--- a/window/window-samples/src/main/res/layout/activity_display_features_config_change.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2020 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"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/rootLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="androidx.window.sample.DisplayFeaturesConfigChangeActivity">
-
- <FrameLayout
- android:id="@+id/featureContainerLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- <LinearLayout
- android:id="@+id/legendLayout"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent">
-
- <TextView
- android:id="@+id/legendTextView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/legend" />
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
-
- <ImageView
- android:id="@+id/foldColorImageView"
- android:layout_width="20dp"
- android:layout_height="20dp"
- android:foreground="@color/colorFeatureFold" />
-
- <TextView
- android:id="@+id/foldColorTextView"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/fold" />
- </LinearLayout>
-
- </LinearLayout>
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/infoLogRecyclerView"
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"/>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/layout/activity_split_activity_layout.xml b/window/window-samples/src/main/res/layout/activity_split_activity_layout.xml
deleted file mode 100644
index d67eefc..0000000
--- a/window/window-samples/src/main/res/layout/activity_split_activity_layout.xml
+++ /dev/null
@@ -1,138 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2020 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.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/root_split_activity_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="10dp">
-
- <TextView
- android:id="@+id/activity_embedded_status_text_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Activity is embedded" />
-
- <CheckBox
- android:id="@+id/splitMainCheckBox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Split Main with other activities" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp"
- android:background="#AAAAAA" />
-
- <Button
- android:id="@+id/launch_b"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_centerHorizontal="true"
- android:text="Launch B" />
-
- <CheckBox
- android:id="@+id/usePlaceholderCheckBox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Use a placeholder for B" />
-
- <CheckBox
- android:id="@+id/useStickyPlaceholderCheckBox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Placeholder is sticky" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp"
- android:background="#AAAAAA" />
-
- <Button
- android:id="@+id/launch_b_and_C"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_centerHorizontal="true"
- android:text="Launch B and C" />
-
- <CheckBox
- android:id="@+id/splitBCCheckBox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Split B with C" />
-
- <CheckBox
- android:id="@+id/finishBCCheckBox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Finish B and C together"
- android:enabled="false" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp"
- android:background="#AAAAAA" />
-
- <Button
- android:id="@+id/launch_e"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_centerHorizontal="true"
- android:text="Launch E" />
-
- <CheckBox
- android:id="@+id/fullscreenECheckBox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Always launch E in fullscreen" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp"
- android:background="#AAAAAA" />
-
- <Button
- android:id="@+id/launch_f"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_centerHorizontal="true"
- android:text="Launch f" />
-
- <Button
- android:id="@+id/launch_f_pending_intent"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_centerHorizontal="true"
- android:text="Launch F via Pending Intent" />
-
- <CheckBox
- android:id="@+id/splitWithFCheckBox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Split everything with F" />
-
-</LinearLayout>
\ No newline at end of file
diff --git a/window/window-testing/src/androidTest/java/androidx/window/testing/layout/StubWindowMetricsCalculatorRuleTest.kt b/window/window-testing/src/androidTest/java/androidx/window/testing/layout/StubWindowMetricsCalculatorRuleTest.kt
index 2118436..3e6544d 100644
--- a/window/window-testing/src/androidTest/java/androidx/window/testing/layout/StubWindowMetricsCalculatorRuleTest.kt
+++ b/window/window-testing/src/androidTest/java/androidx/window/testing/layout/StubWindowMetricsCalculatorRuleTest.kt
@@ -16,6 +16,11 @@
package androidx.window.testing.layout
+import android.content.Context
+import android.graphics.Point
+import android.os.Build
+import android.view.WindowManager
+import androidx.annotation.RequiresApi
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.window.core.ExperimentalWindowApi
@@ -75,6 +80,81 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.R)
+ @Test
+ fun testCurrentWindowMetrics_context_matchesWindowMetricsMetrics_30AndAbove() {
+ Utils.assumePlatformAtOrAbove(Build.VERSION_CODES.R)
+
+ activityRule.scenario.onActivity { activity ->
+ val calculator = WindowMetricsCalculator.getOrCreate()
+ val wm = activity.getSystemService(WindowManager::class.java)
+ val windowMetrics = wm.currentWindowMetrics.bounds
+ val actual = calculator.computeCurrentWindowMetrics(activity as Context)
+
+ assertEquals(0, actual.bounds.left)
+ assertEquals(0, actual.bounds.top)
+ assertEquals(windowMetrics.width(), actual.bounds.right)
+ assertEquals(windowMetrics.height(), actual.bounds.bottom)
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ @Test
+ fun testCurrentWindowMetrics_context_matchesDisplayRealSize_17to29() {
+ Utils.assumePlatformAtOrBelow(Build.VERSION_CODES.Q)
+ Utils.assumePlatformAtOrAbove(Build.VERSION_CODES.JELLY_BEAN_MR1)
+
+ activityRule.scenario.onActivity { activity ->
+ val calculator = WindowMetricsCalculator.getOrCreate()
+ val wm = activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ val displaySize = Point()
+ // DefaultDisplay#getRealSize is used in StubWindowMetricsCalculator for compatibility
+ // with older versions. We're just asserting that the value via
+ // StubWindowMetricsCalculator#computeCurrentWindowMetrics is equal to this.
+ @Suppress("DEPRECATION")
+ wm.defaultDisplay.getRealSize(displaySize)
+ val actual = calculator.computeCurrentWindowMetrics(activity as Context)
+
+ assertEquals(0, actual.bounds.left)
+ assertEquals(0, actual.bounds.top)
+ assertEquals(displaySize.x, actual.bounds.right)
+ assertEquals(displaySize.y, actual.bounds.bottom)
+ }
+ }
+
+ // DefaultDisplay width/height used in tests for API16 and lower
+ @Suppress("DEPRECATION")
+ @Test
+ fun testCurrentWindowMetrics_context_matchesDisplayMetrics_16AndBelow() {
+ Utils.assumePlatformAtOrBelow(Build.VERSION_CODES.JELLY_BEAN)
+
+ activityRule.scenario.onActivity { activity ->
+ val calculator = WindowMetricsCalculator.getOrCreate()
+ val wm = activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ val actual = calculator.computeCurrentWindowMetrics(activity as Context)
+
+ assertEquals(0, actual.bounds.left)
+ assertEquals(0, actual.bounds.top)
+ assertEquals(wm.defaultDisplay.width, actual.bounds.right)
+ assertEquals(wm.defaultDisplay.height, actual.bounds.bottom)
+ }
+ }
+
+ @Test
+ fun testCurrentWindowMetrics_context_matchesMaximumMetrics() {
+ activityRule.scenario.onActivity { activity ->
+ val calculator = WindowMetricsCalculator.getOrCreate()
+
+ val currentMetrics = calculator.computeCurrentWindowMetrics(activity as Context)
+ val maximumMetrics = calculator.computeMaximumWindowMetrics(activity as Context)
+
+ assertEquals(currentMetrics.bounds.left, maximumMetrics.bounds.left)
+ assertEquals(currentMetrics.bounds.top, maximumMetrics.bounds.top)
+ assertEquals(currentMetrics.bounds.right, maximumMetrics.bounds.right)
+ assertEquals(currentMetrics.bounds.bottom, maximumMetrics.bounds.bottom)
+ }
+ }
+
/**
* Tests that when applying a [Statement] then the decorator is removed. This is necessary to
* keep tests hermetic. If this fails on the last test run then the fake implementation of
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityB.kt b/window/window-testing/src/androidTest/java/androidx/window/testing/layout/Utils.kt
similarity index 64%
copy from window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityB.kt
copy to window/window-testing/src/androidTest/java/androidx/window/testing/layout/Utils.kt
index f47ab24..9c57994 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityB.kt
+++ b/window/window-testing/src/androidTest/java/androidx/window/testing/layout/Utils.kt
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-package androidx.window.sample.embedding
+package androidx.window.testing.layout
-import android.graphics.Color
-import android.os.Bundle
+import android.os.Build
+import org.junit.Assume
-open class SplitPipActivityB : SplitPipActivityBase() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- viewBinding.rootSplitActivityLayout.setBackgroundColor(Color.parseColor("#fff3e0"))
+internal object Utils {
+ fun assumePlatformAtOrAbove(version: Int) {
+ Assume.assumeTrue(Build.VERSION.SDK_INT >= version)
+ }
+
+ fun assumePlatformAtOrBelow(version: Int) {
+ Assume.assumeTrue(Build.VERSION.SDK_INT <= version)
}
}
\ No newline at end of file
diff --git a/window/window-testing/src/main/java/androidx/window/testing/layout/StubMetricDecorator.kt b/window/window-testing/src/main/java/androidx/window/testing/layout/StubMetricDecorator.kt
index d02349a..14bf0c7 100644
--- a/window/window-testing/src/main/java/androidx/window/testing/layout/StubMetricDecorator.kt
+++ b/window/window-testing/src/main/java/androidx/window/testing/layout/StubMetricDecorator.kt
@@ -26,6 +26,6 @@
@ExperimentalWindowApi
internal object StubMetricDecorator : WindowMetricsCalculatorDecorator {
override fun decorate(calculator: WindowMetricsCalculator): WindowMetricsCalculator {
- return StubWindowMetricsCalculator
+ return StubWindowMetricsCalculator()
}
}
\ No newline at end of file
diff --git a/window/window-testing/src/main/java/androidx/window/testing/layout/StubWindowMetricsCalculator.kt b/window/window-testing/src/main/java/androidx/window/testing/layout/StubWindowMetricsCalculator.kt
index c9b994e..5a040ab 100644
--- a/window/window-testing/src/main/java/androidx/window/testing/layout/StubWindowMetricsCalculator.kt
+++ b/window/window-testing/src/main/java/androidx/window/testing/layout/StubWindowMetricsCalculator.kt
@@ -17,17 +17,25 @@
package androidx.window.testing.layout
import android.app.Activity
+import android.content.Context
+import android.graphics.Point
import android.graphics.Rect
+import android.os.Build
+import android.view.Display
+import android.view.WindowManager
+import androidx.annotation.RequiresApi
+import androidx.annotation.UiContext
import androidx.window.layout.WindowMetrics
import androidx.window.layout.WindowMetricsCalculator
/**
- * A stub implementation of [WindowMetricsCalculator] that returns the
- * [android.util.DisplayMetrics] for the current and maximum [WindowMetrics]. This is not correct
- * in general terms, as an application may be running in multi-window or otherwise adjusted to not
+ * A stub implementation of [WindowMetricsCalculator] that's intended to be used by Robolectric.
+ * [computeCurrentWindowMetrics] and [computeMaximumWindowMetrics] returns reasonable
+ * [WindowMetrics] for all supported SDK levels, but is not correct in general terms, as an
+ * application or [UiContext] may be running in multi-window mode, or otherwise adjusted to not
* occupy the entire display.
*/
-internal object StubWindowMetricsCalculator : WindowMetricsCalculator {
+internal class StubWindowMetricsCalculator : WindowMetricsCalculator {
override fun computeCurrentWindowMetrics(activity: Activity): WindowMetrics {
val displayMetrics = activity.resources.displayMetrics
@@ -40,4 +48,52 @@
val bounds = Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)
return WindowMetrics(bounds)
}
+
+ // WindowManager#getDefaultDisplay is deprecated but we have this for compatibility with
+ // older versions.
+ @Suppress("DEPRECATION")
+ override fun computeCurrentWindowMetrics(@UiContext context: Context): WindowMetrics {
+ val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ Api30Impl.getWindowMetrics(wm)
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ val displaySize = Point()
+ // We use getRealSize instead of getSize here because:
+ // 1) computeCurrentWindowMetrics and computeMaximumWindowMetrics in this class
+ // always return a measurement equal to the entire display (see class-level
+ // documentation).
+ // 2) getRealSize returns the largest region of the display, whereas getSize returns
+ // the current app window. So to stay consistent with class documentation, we use
+ // getRealSize.
+ Api17Impl.getRealSize(wm.defaultDisplay, displaySize)
+ val bounds = Rect(0, 0, displaySize.x, displaySize.y)
+ WindowMetrics(bounds)
+ } else {
+ val width = wm.defaultDisplay.width
+ val height = wm.defaultDisplay.height
+ val bounds = Rect(0, 0, width, height)
+ WindowMetrics(bounds)
+ }
+ }
+
+ override fun computeMaximumWindowMetrics(@UiContext context: Context): WindowMetrics {
+ return computeCurrentWindowMetrics(context)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ private object Api30Impl {
+ fun getWindowMetrics(windowManager: WindowManager): WindowMetrics {
+ return WindowMetrics(windowManager.currentWindowMetrics.bounds)
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ private object Api17Impl {
+ // getRealSize is deprecated but we have this for compatibility with older versions.
+ @Suppress("DEPRECATION")
+ fun getRealSize(display: Display, point: Point) {
+ display.getRealSize(point)
+ }
+ }
}
\ No newline at end of file
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index a1e039c..2a716a3 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -4,6 +4,7 @@
public final class WindowProperties {
field public static final androidx.window.WindowProperties INSTANCE;
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
+ field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
}
}
@@ -41,6 +42,7 @@
ctor public ActivityRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters);
method public androidx.window.embedding.ActivityRule build();
method public androidx.window.embedding.ActivityRule.Builder setAlwaysExpand(boolean alwaysExpand);
+ method public androidx.window.embedding.ActivityRule.Builder setTag(String tag);
}
public final class ActivityStack {
@@ -61,6 +63,8 @@
}
public abstract class EmbeddingRule {
+ method public final String? getTag();
+ property public final String? tag;
}
public final class RuleController {
@@ -79,6 +83,82 @@
method public java.util.Set<androidx.window.embedding.EmbeddingRule> parseRules(android.content.Context context, @XmlRes int staticRuleResourceId);
}
+ public final class SplitAttributes {
+ method public int getAnimationBackgroundColor();
+ method public androidx.window.embedding.SplitAttributes.LayoutDirection getLayoutDirection();
+ method public androidx.window.embedding.SplitAttributes.SplitType getSplitType();
+ property public final int animationBackgroundColor;
+ property public final androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection;
+ property public final androidx.window.embedding.SplitAttributes.SplitType splitType;
+ field public static final androidx.window.embedding.SplitAttributes.Companion Companion;
+ }
+
+ public static final class SplitAttributes.Builder {
+ ctor public SplitAttributes.Builder();
+ method public androidx.window.embedding.SplitAttributes build();
+ method public androidx.window.embedding.SplitAttributes.Builder setAnimationBackgroundColor(@ColorInt int color);
+ method public androidx.window.embedding.SplitAttributes.Builder setLayoutDirection(androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection);
+ method public androidx.window.embedding.SplitAttributes.Builder setSplitType(androidx.window.embedding.SplitAttributes.SplitType type);
+ }
+
+ public static final class SplitAttributes.Companion {
+ }
+
+ public static final class SplitAttributes.LayoutDirection {
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection BOTTOM_TO_TOP;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection.Companion Companion;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LEFT_TO_RIGHT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LOCALE;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection RIGHT_TO_LEFT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection TOP_TO_BOTTOM;
+ }
+
+ public static final class SplitAttributes.LayoutDirection.Companion {
+ }
+
+ public static class SplitAttributes.SplitType {
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.ExpandContainersSplitType expandContainers();
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.HingeSplitType splitByHinge(optional androidx.window.embedding.SplitAttributes.SplitType fallbackSplitType);
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType splitEqually();
+ field public static final androidx.window.embedding.SplitAttributes.SplitType.Companion Companion;
+ }
+
+ public static final class SplitAttributes.SplitType.Companion {
+ method public androidx.window.embedding.SplitAttributes.SplitType.ExpandContainersSplitType expandContainers();
+ method public androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ method public androidx.window.embedding.SplitAttributes.SplitType.HingeSplitType splitByHinge(optional androidx.window.embedding.SplitAttributes.SplitType fallbackSplitType);
+ method public androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType splitEqually();
+ }
+
+ public static final class SplitAttributes.SplitType.ExpandContainersSplitType extends androidx.window.embedding.SplitAttributes.SplitType {
+ }
+
+ public static final class SplitAttributes.SplitType.HingeSplitType extends androidx.window.embedding.SplitAttributes.SplitType {
+ method public androidx.window.embedding.SplitAttributes.SplitType getFallbackSplitType();
+ property public final androidx.window.embedding.SplitAttributes.SplitType fallbackSplitType;
+ }
+
+ public static final class SplitAttributes.SplitType.RatioSplitType extends androidx.window.embedding.SplitAttributes.SplitType {
+ method public float getRatio();
+ property public final float ratio;
+ }
+
+ public final class SplitAttributesCalculatorParams {
+ method public boolean getAreDefaultConstraintsSatisfied();
+ method public androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public android.content.res.Configuration getParentConfiguration();
+ method public androidx.window.layout.WindowLayoutInfo getParentWindowLayoutInfo();
+ method public androidx.window.layout.WindowMetrics getParentWindowMetrics();
+ method public String? getSplitRuleTag();
+ property public final boolean areDefaultConstraintsSatisfied;
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
+ property public final android.content.res.Configuration parentConfiguration;
+ property public final androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo;
+ property public final androidx.window.layout.WindowMetrics parentWindowMetrics;
+ property public final String? splitRuleTag;
+ }
+
public final class SplitController {
method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
@@ -95,10 +175,10 @@
method public operator boolean contains(android.app.Activity activity);
method public androidx.window.embedding.ActivityStack getPrimaryActivityStack();
method public androidx.window.embedding.ActivityStack getSecondaryActivityStack();
- method public float getSplitRatio();
+ method public androidx.window.embedding.SplitAttributes getSplitAttributes();
property public final androidx.window.embedding.ActivityStack primaryActivityStack;
property public final androidx.window.embedding.ActivityStack secondaryActivityStack;
- property public final float splitRatio;
+ property public final androidx.window.embedding.SplitAttributes splitAttributes;
}
public final class SplitPairFilter {
@@ -116,35 +196,36 @@
public final class SplitPairRule extends androidx.window.embedding.SplitRule {
method public boolean getClearTop();
method public java.util.Set<androidx.window.embedding.SplitPairFilter> getFilters();
- method public int getFinishPrimaryWithSecondary();
- method public int getFinishSecondaryWithPrimary();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithSecondary();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishSecondaryWithPrimary();
property public final boolean clearTop;
property public final java.util.Set<androidx.window.embedding.SplitPairFilter> filters;
- property public final int finishPrimaryWithSecondary;
- property public final int finishSecondaryWithPrimary;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary;
}
public static final class SplitPairRule.Builder {
ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters);
method public androidx.window.embedding.SplitPairRule build();
method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
- method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
- method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
- method public androidx.window.embedding.SplitPairRule.Builder setLayoutDirection(int layoutDirection);
+ method public androidx.window.embedding.SplitPairRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary);
method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
- method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setTag(String? tag);
}
public final class SplitPlaceholderRule extends androidx.window.embedding.SplitRule {
method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
- method public int getFinishPrimaryWithPlaceholder();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithPlaceholder();
method public android.content.Intent getPlaceholderIntent();
method public boolean isSticky();
property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
- property public final int finishPrimaryWithPlaceholder;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder;
property public final boolean isSticky;
property public final android.content.Intent placeholderIntent;
}
@@ -152,33 +233,31 @@
public static final class SplitPlaceholderRule.Builder {
ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent);
method public androidx.window.embedding.SplitPlaceholderRule build();
- method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
- method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int layoutDirection);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
- method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setTag(String? tag);
}
public class SplitRule extends androidx.window.embedding.EmbeddingRule {
- method public final int getLayoutDirection();
+ method public final androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInLandscape();
method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInPortrait();
+ method public final int getMinHeightDp();
method public final int getMinSmallestWidthDp();
method public final int getMinWidthDp();
- method public final float getSplitRatio();
- property public final int layoutDirection;
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInLandscape;
property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInPortrait;
+ property public final int minHeightDp;
property public final int minSmallestWidthDp;
property public final int minWidthDp;
- property public final float splitRatio;
field public static final androidx.window.embedding.SplitRule.Companion Companion;
- field public static final int FINISH_ADJACENT = 2; // 0x2
- field public static final int FINISH_ALWAYS = 1; // 0x1
- field public static final int FINISH_NEVER = 0; // 0x0
field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT;
field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT;
field public static final int SPLIT_MIN_DIMENSION_ALWAYS_ALLOW = 0; // 0x0
@@ -188,6 +267,16 @@
public static final class SplitRule.Companion {
}
+ public static final class SplitRule.FinishBehavior {
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ADJACENT;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ALWAYS;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior.Companion Companion;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior NEVER;
+ }
+
+ public static final class SplitRule.FinishBehavior.Companion {
+ }
+
}
package androidx.window.layout {
@@ -237,6 +326,7 @@
public interface WindowInfoTracker {
method public default static androidx.window.layout.WindowInfoTracker getOrCreate(android.content.Context context);
+ method public default kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(@UiContext android.content.Context context);
method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(android.app.Activity activity);
field public static final androidx.window.layout.WindowInfoTracker.Companion Companion;
}
@@ -257,7 +347,9 @@
public interface WindowMetricsCalculator {
method public androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(@UiContext android.content.Context context);
method public androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(@UiContext android.content.Context context);
method public default static androidx.window.layout.WindowMetricsCalculator getOrCreate();
field public static final androidx.window.layout.WindowMetricsCalculator.Companion Companion;
}
diff --git a/window/window/api/public_plus_experimental_current.txt b/window/window/api/public_plus_experimental_current.txt
index 3973329..986a2d0 100644
--- a/window/window/api/public_plus_experimental_current.txt
+++ b/window/window/api/public_plus_experimental_current.txt
@@ -4,6 +4,7 @@
public final class WindowProperties {
field public static final androidx.window.WindowProperties INSTANCE;
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
+ field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
}
}
@@ -48,6 +49,7 @@
ctor public ActivityRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters);
method public androidx.window.embedding.ActivityRule build();
method public androidx.window.embedding.ActivityRule.Builder setAlwaysExpand(boolean alwaysExpand);
+ method public androidx.window.embedding.ActivityRule.Builder setTag(String tag);
}
public final class ActivityStack {
@@ -68,6 +70,8 @@
}
public abstract class EmbeddingRule {
+ method public final String? getTag();
+ property public final String? tag;
}
public final class RuleController {
@@ -86,11 +90,90 @@
method public java.util.Set<androidx.window.embedding.EmbeddingRule> parseRules(android.content.Context context, @XmlRes int staticRuleResourceId);
}
+ public final class SplitAttributes {
+ method public int getAnimationBackgroundColor();
+ method public androidx.window.embedding.SplitAttributes.LayoutDirection getLayoutDirection();
+ method public androidx.window.embedding.SplitAttributes.SplitType getSplitType();
+ property public final int animationBackgroundColor;
+ property public final androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection;
+ property public final androidx.window.embedding.SplitAttributes.SplitType splitType;
+ field public static final androidx.window.embedding.SplitAttributes.Companion Companion;
+ }
+
+ public static final class SplitAttributes.Builder {
+ ctor public SplitAttributes.Builder();
+ method public androidx.window.embedding.SplitAttributes build();
+ method public androidx.window.embedding.SplitAttributes.Builder setAnimationBackgroundColor(@ColorInt int color);
+ method public androidx.window.embedding.SplitAttributes.Builder setLayoutDirection(androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection);
+ method public androidx.window.embedding.SplitAttributes.Builder setSplitType(androidx.window.embedding.SplitAttributes.SplitType type);
+ }
+
+ public static final class SplitAttributes.Companion {
+ }
+
+ public static final class SplitAttributes.LayoutDirection {
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection BOTTOM_TO_TOP;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection.Companion Companion;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LEFT_TO_RIGHT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LOCALE;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection RIGHT_TO_LEFT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection TOP_TO_BOTTOM;
+ }
+
+ public static final class SplitAttributes.LayoutDirection.Companion {
+ }
+
+ public static class SplitAttributes.SplitType {
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.ExpandContainersSplitType expandContainers();
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.HingeSplitType splitByHinge(optional androidx.window.embedding.SplitAttributes.SplitType fallbackSplitType);
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType splitEqually();
+ field public static final androidx.window.embedding.SplitAttributes.SplitType.Companion Companion;
+ }
+
+ public static final class SplitAttributes.SplitType.Companion {
+ method public androidx.window.embedding.SplitAttributes.SplitType.ExpandContainersSplitType expandContainers();
+ method public androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ method public androidx.window.embedding.SplitAttributes.SplitType.HingeSplitType splitByHinge(optional androidx.window.embedding.SplitAttributes.SplitType fallbackSplitType);
+ method public androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType splitEqually();
+ }
+
+ public static final class SplitAttributes.SplitType.ExpandContainersSplitType extends androidx.window.embedding.SplitAttributes.SplitType {
+ }
+
+ public static final class SplitAttributes.SplitType.HingeSplitType extends androidx.window.embedding.SplitAttributes.SplitType {
+ method public androidx.window.embedding.SplitAttributes.SplitType getFallbackSplitType();
+ property public final androidx.window.embedding.SplitAttributes.SplitType fallbackSplitType;
+ }
+
+ public static final class SplitAttributes.SplitType.RatioSplitType extends androidx.window.embedding.SplitAttributes.SplitType {
+ method public float getRatio();
+ property public final float ratio;
+ }
+
+ public final class SplitAttributesCalculatorParams {
+ method public boolean getAreDefaultConstraintsSatisfied();
+ method public androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public android.content.res.Configuration getParentConfiguration();
+ method public androidx.window.layout.WindowLayoutInfo getParentWindowLayoutInfo();
+ method public androidx.window.layout.WindowMetrics getParentWindowMetrics();
+ method public String? getSplitRuleTag();
+ property public final boolean areDefaultConstraintsSatisfied;
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
+ property public final android.content.res.Configuration parentConfiguration;
+ property public final androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo;
+ property public final androidx.window.layout.WindowMetrics parentWindowMetrics;
+ property public final String? splitRuleTag;
+ }
+
public final class SplitController {
method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+ method @androidx.window.core.ExperimentalWindowApi public void clearSplitAttributesCalculator();
method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
+ method @androidx.window.core.ExperimentalWindowApi public boolean isSplitAttributesCalculatorSupported();
method public boolean isSplitSupported();
method public void removeSplitListener(androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+ method @androidx.window.core.ExperimentalWindowApi public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
field public static final androidx.window.embedding.SplitController.Companion Companion;
}
@@ -102,10 +185,10 @@
method public operator boolean contains(android.app.Activity activity);
method public androidx.window.embedding.ActivityStack getPrimaryActivityStack();
method public androidx.window.embedding.ActivityStack getSecondaryActivityStack();
- method public float getSplitRatio();
+ method public androidx.window.embedding.SplitAttributes getSplitAttributes();
property public final androidx.window.embedding.ActivityStack primaryActivityStack;
property public final androidx.window.embedding.ActivityStack secondaryActivityStack;
- property public final float splitRatio;
+ property public final androidx.window.embedding.SplitAttributes splitAttributes;
}
public final class SplitPairFilter {
@@ -123,35 +206,36 @@
public final class SplitPairRule extends androidx.window.embedding.SplitRule {
method public boolean getClearTop();
method public java.util.Set<androidx.window.embedding.SplitPairFilter> getFilters();
- method public int getFinishPrimaryWithSecondary();
- method public int getFinishSecondaryWithPrimary();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithSecondary();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishSecondaryWithPrimary();
property public final boolean clearTop;
property public final java.util.Set<androidx.window.embedding.SplitPairFilter> filters;
- property public final int finishPrimaryWithSecondary;
- property public final int finishSecondaryWithPrimary;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary;
}
public static final class SplitPairRule.Builder {
ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters);
method public androidx.window.embedding.SplitPairRule build();
method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
- method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
- method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
- method public androidx.window.embedding.SplitPairRule.Builder setLayoutDirection(int layoutDirection);
+ method public androidx.window.embedding.SplitPairRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary);
method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
- method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setTag(String? tag);
}
public final class SplitPlaceholderRule extends androidx.window.embedding.SplitRule {
method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
- method public int getFinishPrimaryWithPlaceholder();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithPlaceholder();
method public android.content.Intent getPlaceholderIntent();
method public boolean isSticky();
property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
- property public final int finishPrimaryWithPlaceholder;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder;
property public final boolean isSticky;
property public final android.content.Intent placeholderIntent;
}
@@ -159,33 +243,31 @@
public static final class SplitPlaceholderRule.Builder {
ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent);
method public androidx.window.embedding.SplitPlaceholderRule build();
- method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
- method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int layoutDirection);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
- method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setTag(String? tag);
}
public class SplitRule extends androidx.window.embedding.EmbeddingRule {
- method public final int getLayoutDirection();
+ method public final androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInLandscape();
method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInPortrait();
+ method public final int getMinHeightDp();
method public final int getMinSmallestWidthDp();
method public final int getMinWidthDp();
- method public final float getSplitRatio();
- property public final int layoutDirection;
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInLandscape;
property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInPortrait;
+ property public final int minHeightDp;
property public final int minSmallestWidthDp;
property public final int minWidthDp;
- property public final float splitRatio;
field public static final androidx.window.embedding.SplitRule.Companion Companion;
- field public static final int FINISH_ADJACENT = 2; // 0x2
- field public static final int FINISH_ALWAYS = 1; // 0x1
- field public static final int FINISH_NEVER = 0; // 0x0
field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT;
field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT;
field public static final int SPLIT_MIN_DIMENSION_ALWAYS_ALLOW = 0; // 0x0
@@ -195,6 +277,16 @@
public static final class SplitRule.Companion {
}
+ public static final class SplitRule.FinishBehavior {
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ADJACENT;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ALWAYS;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior.Companion Companion;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior NEVER;
+ }
+
+ public static final class SplitRule.FinishBehavior.Companion {
+ }
+
}
package androidx.window.layout {
@@ -244,6 +336,7 @@
public interface WindowInfoTracker {
method public default static androidx.window.layout.WindowInfoTracker getOrCreate(android.content.Context context);
+ method public default kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(@UiContext android.content.Context context);
method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(android.app.Activity activity);
field public static final androidx.window.layout.WindowInfoTracker.Companion Companion;
}
@@ -265,7 +358,9 @@
public interface WindowMetricsCalculator {
method public androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(@UiContext android.content.Context context);
method public androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(@UiContext android.content.Context context);
method public default static androidx.window.layout.WindowMetricsCalculator getOrCreate();
field public static final androidx.window.layout.WindowMetricsCalculator.Companion Companion;
}
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index a1e039c..2a716a3 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -4,6 +4,7 @@
public final class WindowProperties {
field public static final androidx.window.WindowProperties INSTANCE;
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
+ field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
}
}
@@ -41,6 +42,7 @@
ctor public ActivityRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters);
method public androidx.window.embedding.ActivityRule build();
method public androidx.window.embedding.ActivityRule.Builder setAlwaysExpand(boolean alwaysExpand);
+ method public androidx.window.embedding.ActivityRule.Builder setTag(String tag);
}
public final class ActivityStack {
@@ -61,6 +63,8 @@
}
public abstract class EmbeddingRule {
+ method public final String? getTag();
+ property public final String? tag;
}
public final class RuleController {
@@ -79,6 +83,82 @@
method public java.util.Set<androidx.window.embedding.EmbeddingRule> parseRules(android.content.Context context, @XmlRes int staticRuleResourceId);
}
+ public final class SplitAttributes {
+ method public int getAnimationBackgroundColor();
+ method public androidx.window.embedding.SplitAttributes.LayoutDirection getLayoutDirection();
+ method public androidx.window.embedding.SplitAttributes.SplitType getSplitType();
+ property public final int animationBackgroundColor;
+ property public final androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection;
+ property public final androidx.window.embedding.SplitAttributes.SplitType splitType;
+ field public static final androidx.window.embedding.SplitAttributes.Companion Companion;
+ }
+
+ public static final class SplitAttributes.Builder {
+ ctor public SplitAttributes.Builder();
+ method public androidx.window.embedding.SplitAttributes build();
+ method public androidx.window.embedding.SplitAttributes.Builder setAnimationBackgroundColor(@ColorInt int color);
+ method public androidx.window.embedding.SplitAttributes.Builder setLayoutDirection(androidx.window.embedding.SplitAttributes.LayoutDirection layoutDirection);
+ method public androidx.window.embedding.SplitAttributes.Builder setSplitType(androidx.window.embedding.SplitAttributes.SplitType type);
+ }
+
+ public static final class SplitAttributes.Companion {
+ }
+
+ public static final class SplitAttributes.LayoutDirection {
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection BOTTOM_TO_TOP;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection.Companion Companion;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LEFT_TO_RIGHT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection LOCALE;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection RIGHT_TO_LEFT;
+ field public static final androidx.window.embedding.SplitAttributes.LayoutDirection TOP_TO_BOTTOM;
+ }
+
+ public static final class SplitAttributes.LayoutDirection.Companion {
+ }
+
+ public static class SplitAttributes.SplitType {
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.ExpandContainersSplitType expandContainers();
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.HingeSplitType splitByHinge(optional androidx.window.embedding.SplitAttributes.SplitType fallbackSplitType);
+ method public static final androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType splitEqually();
+ field public static final androidx.window.embedding.SplitAttributes.SplitType.Companion Companion;
+ }
+
+ public static final class SplitAttributes.SplitType.Companion {
+ method public androidx.window.embedding.SplitAttributes.SplitType.ExpandContainersSplitType expandContainers();
+ method public androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType ratio(@FloatRange(from=0.0, to=1.0, fromInclusive=false, toInclusive=false) float ratio);
+ method public androidx.window.embedding.SplitAttributes.SplitType.HingeSplitType splitByHinge(optional androidx.window.embedding.SplitAttributes.SplitType fallbackSplitType);
+ method public androidx.window.embedding.SplitAttributes.SplitType.RatioSplitType splitEqually();
+ }
+
+ public static final class SplitAttributes.SplitType.ExpandContainersSplitType extends androidx.window.embedding.SplitAttributes.SplitType {
+ }
+
+ public static final class SplitAttributes.SplitType.HingeSplitType extends androidx.window.embedding.SplitAttributes.SplitType {
+ method public androidx.window.embedding.SplitAttributes.SplitType getFallbackSplitType();
+ property public final androidx.window.embedding.SplitAttributes.SplitType fallbackSplitType;
+ }
+
+ public static final class SplitAttributes.SplitType.RatioSplitType extends androidx.window.embedding.SplitAttributes.SplitType {
+ method public float getRatio();
+ property public final float ratio;
+ }
+
+ public final class SplitAttributesCalculatorParams {
+ method public boolean getAreDefaultConstraintsSatisfied();
+ method public androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
+ method public android.content.res.Configuration getParentConfiguration();
+ method public androidx.window.layout.WindowLayoutInfo getParentWindowLayoutInfo();
+ method public androidx.window.layout.WindowMetrics getParentWindowMetrics();
+ method public String? getSplitRuleTag();
+ property public final boolean areDefaultConstraintsSatisfied;
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
+ property public final android.content.res.Configuration parentConfiguration;
+ property public final androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo;
+ property public final androidx.window.layout.WindowMetrics parentWindowMetrics;
+ property public final String? splitRuleTag;
+ }
+
public final class SplitController {
method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
@@ -95,10 +175,10 @@
method public operator boolean contains(android.app.Activity activity);
method public androidx.window.embedding.ActivityStack getPrimaryActivityStack();
method public androidx.window.embedding.ActivityStack getSecondaryActivityStack();
- method public float getSplitRatio();
+ method public androidx.window.embedding.SplitAttributes getSplitAttributes();
property public final androidx.window.embedding.ActivityStack primaryActivityStack;
property public final androidx.window.embedding.ActivityStack secondaryActivityStack;
- property public final float splitRatio;
+ property public final androidx.window.embedding.SplitAttributes splitAttributes;
}
public final class SplitPairFilter {
@@ -116,35 +196,36 @@
public final class SplitPairRule extends androidx.window.embedding.SplitRule {
method public boolean getClearTop();
method public java.util.Set<androidx.window.embedding.SplitPairFilter> getFilters();
- method public int getFinishPrimaryWithSecondary();
- method public int getFinishSecondaryWithPrimary();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithSecondary();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishSecondaryWithPrimary();
property public final boolean clearTop;
property public final java.util.Set<androidx.window.embedding.SplitPairFilter> filters;
- property public final int finishPrimaryWithSecondary;
- property public final int finishSecondaryWithPrimary;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary;
}
public static final class SplitPairRule.Builder {
ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters);
method public androidx.window.embedding.SplitPairRule build();
method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
- method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
- method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
- method public androidx.window.embedding.SplitPairRule.Builder setLayoutDirection(int layoutDirection);
+ method public androidx.window.embedding.SplitPairRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithSecondary);
+ method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(androidx.window.embedding.SplitRule.FinishBehavior finishSecondaryWithPrimary);
method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
- method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
+ method public androidx.window.embedding.SplitPairRule.Builder setTag(String? tag);
}
public final class SplitPlaceholderRule extends androidx.window.embedding.SplitRule {
method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
- method public int getFinishPrimaryWithPlaceholder();
+ method public androidx.window.embedding.SplitRule.FinishBehavior getFinishPrimaryWithPlaceholder();
method public android.content.Intent getPlaceholderIntent();
method public boolean isSticky();
property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
- property public final int finishPrimaryWithPlaceholder;
+ property public final androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder;
property public final boolean isSticky;
property public final android.content.Intent placeholderIntent;
}
@@ -152,33 +233,31 @@
public static final class SplitPlaceholderRule.Builder {
ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent);
method public androidx.window.embedding.SplitPlaceholderRule build();
- method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
- method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int layoutDirection);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setDefaultSplitAttributes(androidx.window.embedding.SplitAttributes defaultSplitAttributes);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(androidx.window.embedding.SplitRule.FinishBehavior finishPrimaryWithPlaceholder);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinHeightDp(@IntRange(from=0L) int minHeightDp);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
- method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
+ method public androidx.window.embedding.SplitPlaceholderRule.Builder setTag(String? tag);
}
public class SplitRule extends androidx.window.embedding.EmbeddingRule {
- method public final int getLayoutDirection();
+ method public final androidx.window.embedding.SplitAttributes getDefaultSplitAttributes();
method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInLandscape();
method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInPortrait();
+ method public final int getMinHeightDp();
method public final int getMinSmallestWidthDp();
method public final int getMinWidthDp();
- method public final float getSplitRatio();
- property public final int layoutDirection;
+ property public final androidx.window.embedding.SplitAttributes defaultSplitAttributes;
property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInLandscape;
property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInPortrait;
+ property public final int minHeightDp;
property public final int minSmallestWidthDp;
property public final int minWidthDp;
- property public final float splitRatio;
field public static final androidx.window.embedding.SplitRule.Companion Companion;
- field public static final int FINISH_ADJACENT = 2; // 0x2
- field public static final int FINISH_ALWAYS = 1; // 0x1
- field public static final int FINISH_NEVER = 0; // 0x0
field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT;
field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT;
field public static final int SPLIT_MIN_DIMENSION_ALWAYS_ALLOW = 0; // 0x0
@@ -188,6 +267,16 @@
public static final class SplitRule.Companion {
}
+ public static final class SplitRule.FinishBehavior {
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ADJACENT;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior ALWAYS;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior.Companion Companion;
+ field public static final androidx.window.embedding.SplitRule.FinishBehavior NEVER;
+ }
+
+ public static final class SplitRule.FinishBehavior.Companion {
+ }
+
}
package androidx.window.layout {
@@ -237,6 +326,7 @@
public interface WindowInfoTracker {
method public default static androidx.window.layout.WindowInfoTracker getOrCreate(android.content.Context context);
+ method public default kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(@UiContext android.content.Context context);
method public kotlinx.coroutines.flow.Flow<androidx.window.layout.WindowLayoutInfo> windowLayoutInfo(android.app.Activity activity);
field public static final androidx.window.layout.WindowInfoTracker.Companion Companion;
}
@@ -257,7 +347,9 @@
public interface WindowMetricsCalculator {
method public androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeCurrentWindowMetrics(@UiContext android.content.Context context);
method public androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(android.app.Activity activity);
+ method public default androidx.window.layout.WindowMetrics computeMaximumWindowMetrics(@UiContext android.content.Context context);
method public default static androidx.window.layout.WindowMetricsCalculator getOrCreate();
field public static final androidx.window.layout.WindowMetricsCalculator.Companion Companion;
}
diff --git a/window/window/build.gradle b/window/window/build.gradle
index 5236d42..cdbbf0f 100644
--- a/window/window/build.gradle
+++ b/window/window/build.gradle
@@ -45,12 +45,13 @@
dependencies {
api(libs.kotlinStdlib)
api(libs.kotlinCoroutinesAndroid)
- implementation("androidx.annotation:annotation:1.2.0")
+ implementation("androidx.annotation:annotation:1.3.0")
implementation("androidx.collection:collection:1.1.0")
implementation("androidx.core:core:1.8.0")
+ implementation("androidx.window.extensions.core:core:1.0.0-alpha01")
compileOnly(project(":window:sidecar:sidecar"))
- compileOnly("androidx.window.extensions:extensions:1.1.0-alpha02")
+ compileOnly(project(":window:extensions:extensions"))
testImplementation(libs.testCore)
testImplementation(libs.testRunner)
@@ -61,7 +62,8 @@
testImplementation(libs.mockitoKotlin4)
testImplementation(libs.kotlinCoroutinesTest)
testImplementation(compileOnly(project(":window:sidecar:sidecar")))
- testImplementation(compileOnly("androidx.window.extensions:extensions:1.1.0-alpha02"))
+ testImplementation(compileOnly(project(":window:extensions:extensions")))
+ testImplementation(implementation("androidx.window.extensions.core:core:1.0.0-alpha01"))
androidTestImplementation(libs.testCore)
androidTestImplementation(libs.kotlinTestJunit)
@@ -75,8 +77,9 @@
androidTestImplementation(libs.multidex)
androidTestImplementation(libs.truth)
androidTestImplementation(libs.junit) // Needed for Assert.assertThrows
+ androidTestImplementation(compileOnly(project(":window:extensions:extensions")))
androidTestImplementation(compileOnly(project(":window:sidecar:sidecar")))
- androidTestImplementation(compileOnly("androidx.window.extensions:extensions:1.1.0-alpha02"))
+ androidTestImplementation(implementation("androidx.window.extensions.core:core:1.0.0-alpha01"))
samples(project(":window:window-samples"))
}
diff --git a/window/window/samples/build.gradle b/window/window/samples/build.gradle
new file mode 100644
index 0000000..a391e0a
--- /dev/null
+++ b/window/window/samples/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+dependencies {
+ api(libs.kotlinStdlib)
+ api(project(":window:window"))
+
+ compileOnly(project(":annotation:annotation-sampled"))
+}
+
+android {
+ namespace "androidx.window.samples"
+}
+
+androidx {
+ name = "Jetpack WindowManager Library Samples"
+ type = LibraryType.SAMPLES
+ inceptionYear = "2022"
+ description = "Code samples for WindowManager Jetpack library."
+}
diff --git a/window/window/samples/src/main/java/androidx/window/samples/embedding/SplitAttributesCalculatorSamples.kt b/window/window/samples/src/main/java/androidx/window/samples/embedding/SplitAttributesCalculatorSamples.kt
new file mode 100644
index 0000000..916701e
--- /dev/null
+++ b/window/window/samples/src/main/java/androidx/window/samples/embedding/SplitAttributesCalculatorSamples.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.window.samples.embedding
+
+import android.app.Application
+import android.graphics.Color
+import androidx.annotation.Sampled
+import androidx.window.core.ExperimentalWindowApi
+import androidx.window.embedding.SplitAttributes
+import androidx.window.embedding.SplitController
+import androidx.window.layout.FoldingFeature
+
+@OptIn(ExperimentalWindowApi::class)
+@Sampled
+fun splitAttributesCalculatorSample() {
+ SplitController.getInstance(context)
+ .setSplitAttributesCalculator { params ->
+ val tag = params.splitRuleTag
+ val parentWindowMetrics = params.parentWindowMetrics
+ val parentConfig = params.parentConfiguration
+ val foldingFeatures = params.parentWindowLayoutInfo.displayFeatures
+ .filterIsInstance<FoldingFeature>()
+ val foldingState = if (foldingFeatures.size == 1) foldingFeatures[0] else null
+ // Tag can be used to filter the SplitRule to apply the SplitAttributes
+ if (TAG_SPLIT_RULE_MAIN != tag && params.areDefaultConstraintsSatisfied) {
+ return@setSplitAttributesCalculator params.defaultSplitAttributes
+ }
+
+ // This sample will make the app show a layout to
+ // - split the task bounds vertically if the device is in landscape
+ // - fill the task bounds if the device is in portrait and its folding state does not
+ // split the screen
+ // - split the task bounds horizontally in tabletop mode
+ val bounds = parentWindowMetrics.bounds
+ if (foldingState?.isSeparating == true) {
+ // Split the parent container that followed by the hinge if the hinge separates the
+ // parent window.
+ return@setSplitAttributesCalculator SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.splitByHinge())
+ .setLayoutDirection(
+ if (foldingState.orientation == FoldingFeature.Orientation.HORIZONTAL) {
+ SplitAttributes.LayoutDirection.TOP_TO_BOTTOM
+ } else {
+ SplitAttributes.LayoutDirection.LOCALE
+ }
+ )
+ // Set the color to use when switching between vertical and horizontal
+ .setAnimationBackgroundColor(Color.GRAY)
+ .build()
+ }
+ return@setSplitAttributesCalculator if (
+ parentConfig.screenWidthDp >= 600 && bounds.width() >= bounds.height()
+ ) {
+ // Split the parent container equally and vertically if the device is in landscape.
+ SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+ .setAnimationBackgroundColor(Color.GRAY)
+ .build()
+ } else {
+ // Expand containers if the device is in portrait or the width is less than 600 dp.
+ SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.expandContainers())
+ .build()
+ }
+ }
+}
+
+@OptIn(ExperimentalWindowApi::class)
+@Sampled
+fun splitWithOrientations() {
+ SplitController.getInstance(context)
+ .setSplitAttributesCalculator { params ->
+ // A sample to split with the dimension that larger than 600 DP. If there's no dimension
+ // larger than 600 DP, show the presentation to fill the task bounds.
+ val parentConfiguration = params.parentConfiguration
+ val builder = SplitAttributes.Builder()
+ return@setSplitAttributesCalculator if (parentConfiguration.screenWidthDp >= 600) {
+ builder
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+ // Set the color to use when switching between vertical and horizontal
+ .setAnimationBackgroundColor(Color.GRAY)
+ .build()
+ } else if (parentConfiguration.screenHeightDp >= 600) {
+ builder
+ .setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+ // Set the color to use when switching between vertical and horizontal
+ .setAnimationBackgroundColor(Color.GRAY)
+ .build()
+ } else {
+ // Fallback to expand the secondary container
+ builder
+ .setSplitType(SplitAttributes.SplitType.expandContainers())
+ .build()
+ }
+ }
+}
+
+@OptIn(ExperimentalWindowApi::class)
+@Sampled
+fun expandContainersInPortrait() {
+ SplitController.getInstance(context)
+ .setSplitAttributesCalculator { params ->
+ // A sample to always fill task bounds when the device is in portrait.
+ val tag = params.splitRuleTag
+ val bounds = params.parentWindowMetrics.bounds
+ val defaultSplitAttributes = params.defaultSplitAttributes
+ val areDefaultConstraintsSatisfied = params.areDefaultConstraintsSatisfied
+
+ val expandContainersAttrs = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.expandContainers())
+ .build()
+ if (!areDefaultConstraintsSatisfied) {
+ return@setSplitAttributesCalculator expandContainersAttrs
+ }
+ // Always expand containers for the splitRule tagged as
+ // TAG_SPLIT_RULE_EXPAND_IN_PORTRAIT if the device is in portrait
+ // even if [areDefaultConstraintsSatisfied] reports true.
+ if (bounds.height() > bounds.width() && TAG_SPLIT_RULE_EXPAND_IN_PORTRAIT.equals(tag)) {
+ return@setSplitAttributesCalculator expandContainersAttrs
+ }
+ // Otherwise, use the default splitAttributes.
+ return@setSplitAttributesCalculator defaultSplitAttributes
+ }
+}
+
+/** Assume it's a valid [Application]... */
+val context = Application()
+const val TAG_SPLIT_RULE_MAIN = "main"
+const val TAG_SPLIT_RULE_EXPAND_IN_PORTRAIT = "expand_in_portrait"
\ No newline at end of file
diff --git a/window/window/src/androidTest/AndroidManifest.xml b/window/window/src/androidTest/AndroidManifest.xml
index d543136..cd1d81e 100644
--- a/window/window/src/androidTest/AndroidManifest.xml
+++ b/window/window/src/androidTest/AndroidManifest.xml
@@ -25,5 +25,8 @@
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
android:value="true" />
+ <property
+ android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
+ android:value="true" />
</application>
</manifest>
diff --git a/window/window/src/androidTest/java/androidx/window/WindowPropertiesTest.kt b/window/window/src/androidTest/java/androidx/window/WindowPropertiesTest.kt
index b1c97d0..34eb50f 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowPropertiesTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/WindowPropertiesTest.kt
@@ -49,6 +49,24 @@
}
}
+ @Test
+ fun test_property_activity_embedding_splits() {
+ assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+ // No-op, but to suppress lint
+ return
+ }
+ activityRule.scenario.onActivity { activity ->
+ // Should be true as defined in AndroidManifest.xml
+ assertTrue(
+ getProperty(
+ activity,
+ WindowProperties.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
+ )
+ )
+ }
+ }
+
@RequiresApi(Build.VERSION_CODES.S)
@Throws(PackageManager.NameNotFoundException::class)
private fun getProperty(context: Context, propertyName: String): Boolean {
diff --git a/window/window/src/androidTest/java/androidx/window/WindowTestUtils.kt b/window/window/src/androidTest/java/androidx/window/WindowTestUtils.kt
new file mode 100644
index 0000000..d9ae80b
--- /dev/null
+++ b/window/window/src/androidTest/java/androidx/window/WindowTestUtils.kt
@@ -0,0 +1,42 @@
+package androidx.window
+
+import android.app.Application
+import android.content.Context
+import android.hardware.display.DisplayManager
+import android.os.Build
+import android.view.Display
+import android.view.WindowManager
+import androidx.annotation.RequiresApi
+import androidx.test.core.app.ApplicationProvider
+import androidx.window.core.ExtensionsUtil
+import org.junit.Assume.assumeTrue
+
+open class WindowTestUtils {
+ companion object {
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ fun createOverlayWindowContext(): Context {
+ val context = ApplicationProvider.getApplicationContext<Application>()
+ return context.createDisplayContext(
+ context.getSystemService(DisplayManager::class.java)
+ .getDisplay(Display.DEFAULT_DISPLAY)
+ ).createWindowContext(
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ /* options= */ null
+ )
+ }
+
+ @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+ fun assumeAtLeastVendorApiLevel(min: Int) {
+ val apiLevel = ExtensionsUtil.safeVendorApiLevel
+ assumeTrue(apiLevel >= min)
+ }
+
+ @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+ fun assumeBeforeVendorApiLevel(max: Int) {
+ val apiLevel = ExtensionsUtil.safeVendorApiLevel
+ assumeTrue(apiLevel < max)
+ assumeTrue(apiLevel > 0)
+ }
+ }
+}
\ No newline at end of file
diff --git a/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingAdapterTest.kt b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingAdapterTest.kt
new file mode 100644
index 0000000..32e974e
--- /dev/null
+++ b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingAdapterTest.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.embedding
+
+import androidx.window.extensions.embedding.ActivityStack as OEMActivityStack
+import androidx.window.extensions.embedding.SplitAttributes as OEMSplitAttributes
+import androidx.window.extensions.embedding.SplitInfo as OEMSplitInfo
+import android.graphics.Color
+import androidx.window.WindowTestUtils
+import androidx.window.core.PredicateAdapter
+import androidx.window.embedding.SplitAttributes.SplitType
+import androidx.window.extensions.WindowExtensions
+import androidx.window.extensions.embedding.SplitAttributes.LayoutDirection.TOP_TO_BOTTOM
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+/** Tests for [EmbeddingAdapter] */
+class EmbeddingAdapterTest {
+ private lateinit var adapter: EmbeddingAdapter
+
+ @Before
+ fun setUp() {
+ adapter = EmbeddingBackend::class.java.classLoader?.let { loader ->
+ EmbeddingAdapter(PredicateAdapter(loader))
+ }!!
+ }
+
+ @Test
+ fun testTranslateSplitInfoWithDefaultAttrs() {
+ WindowTestUtils.assumeAtLeastVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_2)
+
+ val mockPrimaryActivityStack = mock<OEMActivityStack>().apply {
+ whenever(activities).thenReturn(emptyList())
+ whenever(isEmpty).thenReturn(true)
+ }
+ val mockSecondaryActivityStack = mock<OEMActivityStack>().apply {
+ whenever(activities).thenReturn(emptyList())
+ whenever(isEmpty).thenReturn(true)
+ }
+ val mockSplitAttributes = mock<OEMSplitAttributes>().apply {
+ whenever(splitType).thenReturn(RatioSplitType.splitEqually())
+ }
+ val oemSplitInfo = mock<OEMSplitInfo>().apply {
+ whenever(primaryActivityStack).thenReturn(mockPrimaryActivityStack)
+ whenever(secondaryActivityStack).thenReturn(mockSecondaryActivityStack)
+ whenever(splitAttributes).thenReturn(mockSplitAttributes)
+ }
+ val expectedSplitInfo = SplitInfo(
+ ActivityStack(ArrayList(), isEmpty = true),
+ ActivityStack(ArrayList(), isEmpty = true),
+ SplitAttributes.Builder()
+ .setSplitType(SplitType.splitEqually())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+ .setAnimationBackgroundColor(0)
+ .build()
+ )
+ assertEquals(listOf(expectedSplitInfo), adapter.translate(listOf(oemSplitInfo)))
+ }
+
+ @Test
+ fun testTranslateSplitInfoWithExpandingContainers() {
+ WindowTestUtils.assumeAtLeastVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_2)
+
+ val mockPrimaryActivityStack = mock<OEMActivityStack>().apply {
+ whenever(activities).thenReturn(emptyList())
+ whenever(isEmpty).thenReturn(true)
+ }
+ val mockSecondaryActivityStack = mock<OEMActivityStack>().apply {
+ whenever(activities).thenReturn(emptyList())
+ whenever(isEmpty).thenReturn(true)
+ }
+ val mockSplitAttributes = mock<OEMSplitAttributes>().apply {
+ whenever(splitType).thenReturn(ExpandContainersSplitType())
+ }
+ val oemSplitInfo = mock<OEMSplitInfo>().apply {
+ whenever(primaryActivityStack).thenReturn(mockPrimaryActivityStack)
+ whenever(secondaryActivityStack).thenReturn(mockSecondaryActivityStack)
+ whenever(splitAttributes).thenReturn(mockSplitAttributes)
+ }
+ val expectedSplitInfo = SplitInfo(
+ ActivityStack(ArrayList(), isEmpty = true),
+ ActivityStack(ArrayList(), isEmpty = true),
+ SplitAttributes.Builder()
+ .setSplitType(SplitType.expandContainers())
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+ .build()
+ )
+ assertEquals(listOf(expectedSplitInfo), adapter.translate(listOf(oemSplitInfo)))
+ }
+
+ @Suppress("DEPRECATION")
+ @Test
+ fun testTranslateSplitInfoWithApiLevel1() {
+ WindowTestUtils.assumeBeforeVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_2)
+
+ val expectedSplitRatio = 0.3f
+ val mockPrimaryActivityStack = mock<OEMActivityStack>().apply {
+ whenever(activities).thenReturn(emptyList())
+ whenever(isEmpty).thenReturn(true)
+ }
+ val mockSecondaryActivityStack = mock<OEMActivityStack>().apply {
+ whenever(activities).thenReturn(emptyList())
+ whenever(isEmpty).thenReturn(true)
+ }
+ val oemSplitInfo = mock<OEMSplitInfo>().apply {
+ whenever(primaryActivityStack).thenReturn(mockPrimaryActivityStack)
+ whenever(secondaryActivityStack).thenReturn(mockSecondaryActivityStack)
+ whenever(splitRatio).thenReturn(expectedSplitRatio)
+ }
+
+ val expectedSplitInfo = SplitInfo(
+ ActivityStack(ArrayList(), isEmpty = true),
+ ActivityStack(ArrayList(), isEmpty = true),
+ SplitAttributes.Builder()
+ .setSplitType(SplitType.ratio(expectedSplitRatio))
+ // OEMSplitInfo with Vendor API level 1 doesn't provide layoutDirection.
+ .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+ .build()
+ )
+ assertEquals(listOf(expectedSplitInfo), adapter.translate(listOf(oemSplitInfo)))
+ }
+
+ @Test
+ fun testTranslateSplitInfoWithApiLevel2() {
+ WindowTestUtils.assumeAtLeastVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_2)
+
+ val mockPrimaryActivityStack = mock<OEMActivityStack>().apply {
+ whenever(activities).thenReturn(emptyList())
+ whenever(isEmpty).thenReturn(true)
+ }
+ val mockSecondaryActivityStack = mock<OEMActivityStack>().apply {
+ whenever(activities).thenReturn(emptyList())
+ whenever(isEmpty).thenReturn(true)
+ }
+ val mockSplitAttributes = mock<OEMSplitAttributes>().apply {
+ whenever(splitType).thenReturn(HingeSplitType(RatioSplitType(0.3f)))
+ whenever(layoutDirection).thenReturn(TOP_TO_BOTTOM)
+ whenever(animationBackgroundColor).thenReturn(Color.YELLOW)
+ }
+ val oemSplitInfo = mock<OEMSplitInfo>().apply {
+ whenever(primaryActivityStack).thenReturn(mockPrimaryActivityStack)
+ whenever(secondaryActivityStack).thenReturn(mockSecondaryActivityStack)
+ whenever(splitAttributes).thenReturn(mockSplitAttributes)
+ }
+ val expectedSplitInfo = SplitInfo(
+ ActivityStack(ArrayList(), isEmpty = true),
+ ActivityStack(ArrayList(), isEmpty = true),
+ SplitAttributes.Builder()
+ .setSplitType(SplitType.splitByHinge(SplitType.ratio(0.3f)))
+ .setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+ .setAnimationBackgroundColor(Color.YELLOW)
+ .build()
+ )
+ assertEquals(listOf(expectedSplitInfo), adapter.translate(listOf(oemSplitInfo)))
+ }
+}
\ No newline at end of file
diff --git a/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
index a60f358..62dcd3b 100644
--- a/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
+++ b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
@@ -19,24 +19,34 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.graphics.Color
import android.graphics.Rect
-import android.util.LayoutDirection
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.test.core.app.ApplicationProvider
import androidx.window.embedding.EmbeddingAspectRatio.Companion.ALWAYS_ALLOW
import androidx.window.embedding.EmbeddingAspectRatio.Companion.ALWAYS_DISALLOW
import androidx.window.embedding.EmbeddingAspectRatio.Companion.ratio
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.BOTTOM_TO_TOP
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LEFT_TO_RIGHT
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LOCALE
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.RIGHT_TO_LEFT
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.TOP_TO_BOTTOM
import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
-import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_DP_DEFAULT
-import androidx.window.embedding.SplitRule.Companion.FINISH_ADJACENT
-import androidx.window.embedding.SplitRule.Companion.FINISH_ALWAYS
-import androidx.window.embedding.SplitRule.Companion.FINISH_NEVER
import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW
+import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_DP_DEFAULT
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.ADJACENT
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.ALWAYS
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.NEVER
import androidx.window.test.R
+import junit.framework.TestCase.assertNull
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Before
import org.junit.Test
/**
@@ -47,7 +57,17 @@
*/
class EmbeddingRuleConstructionTests {
private val application = ApplicationProvider.getApplicationContext<Context>()
+ private val ruleController = RuleController.getInstance(application)
private val density = application.resources.displayMetrics.density
+ private lateinit var validBounds: Rect
+ private lateinit var invalidBounds: Rect
+
+ @Before
+ fun setUp() {
+ validBounds = minValidWindowBounds()
+ invalidBounds = almostValidWindowBounds()
+ ruleController.clearRules()
+ }
/**
* Verifies that default params are set correctly when reading {@link SplitPairRule} from XML.
@@ -58,17 +78,23 @@
.parseRules(application, R.xml.test_split_config_default_split_pair_rule)
assertEquals(1, rules.size)
val rule: SplitPairRule = rules.first() as SplitPairRule
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.5f))
+ .setLayoutDirection(LOCALE)
+ .setAnimationBackgroundColor(0)
+ .build()
+ assertNull(rule.tag)
assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minWidthDp)
+ assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minHeightDp)
assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minSmallestWidthDp)
assertEquals(SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT, rule.maxAspectRatioInPortrait)
assertEquals(SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT, rule.maxAspectRatioInLandscape)
- assertEquals(FINISH_NEVER, rule.finishPrimaryWithSecondary)
- assertEquals(FINISH_ALWAYS, rule.finishSecondaryWithPrimary)
+ assertEquals(NEVER, rule.finishPrimaryWithSecondary)
+ assertEquals(ALWAYS, rule.finishSecondaryWithPrimary)
assertEquals(false, rule.clearTop)
- assertEquals(0.5f, rule.splitRatio)
- assertEquals(LayoutDirection.LOCALE, rule.layoutDirection)
- assertTrue(rule.checkParentBounds(density, minValidWindowBounds()))
- assertFalse(rule.checkParentBounds(density, almostValidWindowBounds()))
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
+ assertTrue(rule.checkParentBounds(density, validBounds))
+ assertFalse(rule.checkParentBounds(density, invalidBounds))
}
/**
@@ -81,15 +107,41 @@
.parseRules(application, R.xml.test_split_config_custom_split_pair_rule)
assertEquals(1, rules.size)
val rule: SplitPairRule = rules.first() as SplitPairRule
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.1f))
+ .setLayoutDirection(RIGHT_TO_LEFT)
+ .build()
+ assertEquals("rule2", rule.tag)
assertEquals(123, rule.minWidthDp)
- assertEquals(456, rule.minSmallestWidthDp)
+ assertEquals(456, rule.minHeightDp)
+ assertEquals(789, rule.minSmallestWidthDp)
assertEquals(1.23f, rule.maxAspectRatioInPortrait.value)
assertEquals(ALWAYS_DISALLOW, rule.maxAspectRatioInLandscape)
- assertEquals(FINISH_ALWAYS, rule.finishPrimaryWithSecondary)
- assertEquals(FINISH_NEVER, rule.finishSecondaryWithPrimary)
+ assertEquals(ALWAYS, rule.finishPrimaryWithSecondary)
+ assertEquals(NEVER, rule.finishSecondaryWithPrimary)
assertEquals(true, rule.clearTop)
- assertEquals(0.1f, rule.splitRatio)
- assertEquals(LayoutDirection.RTL, rule.layoutDirection)
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
+ }
+
+ /** Verifies that horizontal layout are set correctly when reading [SplitPairRule] from XML. */
+ @Test
+ fun testHorizontalLayout_SplitPairRule_Xml() {
+ val rules = RuleController
+ .parseRules(application, R.xml.test_split_config_split_pair_rule_horizontal_layout)
+ assertEquals(1, rules.size)
+ val rule: SplitPairRule = rules.first() as SplitPairRule
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.3f))
+ .setLayoutDirection(TOP_TO_BOTTOM)
+ .setAnimationBackgroundColor(Color.BLUE)
+ .build()
+ assertEquals(TEST_TAG, rule.tag)
+ assertEquals(NEVER, rule.finishPrimaryWithSecondary)
+ assertEquals(ALWAYS, rule.finishSecondaryWithPrimary)
+ assertEquals(false, rule.clearTop)
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
+ assertTrue(rule.checkParentBounds(density, validBounds))
+ assertFalse(rule.checkParentBounds(density, invalidBounds))
}
/**
@@ -99,17 +151,23 @@
@Test
fun testDefaults_SplitPairRule_Builder() {
val rule = SplitPairRule.Builder(HashSet()).build()
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.5f))
+ .setLayoutDirection(LOCALE)
+ .setAnimationBackgroundColor(0)
+ .build()
+ assertNull(rule.tag)
assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minWidthDp)
+ assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minHeightDp)
assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minSmallestWidthDp)
assertEquals(SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT, rule.maxAspectRatioInPortrait)
assertEquals(SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT, rule.maxAspectRatioInLandscape)
- assertEquals(FINISH_NEVER, rule.finishPrimaryWithSecondary)
- assertEquals(FINISH_ALWAYS, rule.finishSecondaryWithPrimary)
+ assertEquals(NEVER, rule.finishPrimaryWithSecondary)
+ assertEquals(ALWAYS, rule.finishSecondaryWithPrimary)
assertEquals(false, rule.clearTop)
- assertEquals(0.5f, rule.splitRatio)
- assertEquals(LayoutDirection.LOCALE, rule.layoutDirection)
- assertTrue(rule.checkParentBounds(density, minValidWindowBounds()))
- assertFalse(rule.checkParentBounds(density, almostValidWindowBounds()))
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
+ assertTrue(rule.checkParentBounds(density, validBounds))
+ assertFalse(rule.checkParentBounds(density, invalidBounds))
}
/**
@@ -119,6 +177,11 @@
@Test
fun test_SplitPairRule_Builder() {
val filters = HashSet<SplitPairFilter>()
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.3f))
+ .setLayoutDirection(LEFT_TO_RIGHT)
+ .setAnimationBackgroundColor(Color.GREEN)
+ .build()
filters.add(
SplitPairFilter(
ComponentName("a", "b"),
@@ -128,23 +191,25 @@
)
val rule = SplitPairRule.Builder(filters)
.setMinWidthDp(123)
- .setMinSmallestWidthDp(456)
+ .setMinHeightDp(456)
+ .setMinSmallestWidthDp(789)
.setMaxAspectRatioInPortrait(ratio(1.23f))
.setMaxAspectRatioInLandscape(ratio(4.56f))
- .setFinishPrimaryWithSecondary(FINISH_ADJACENT)
- .setFinishSecondaryWithPrimary(FINISH_ADJACENT)
+ .setFinishPrimaryWithSecondary(ADJACENT)
+ .setFinishSecondaryWithPrimary(ADJACENT)
.setClearTop(true)
- .setSplitRatio(0.3f)
- .setLayoutDirection(LayoutDirection.LTR)
+ .setDefaultSplitAttributes(expectedSplitLayout)
+ .setTag(TEST_TAG)
.build()
- assertEquals(FINISH_ADJACENT, rule.finishPrimaryWithSecondary)
- assertEquals(FINISH_ADJACENT, rule.finishSecondaryWithPrimary)
+ assertEquals(ADJACENT, rule.finishPrimaryWithSecondary)
+ assertEquals(ADJACENT, rule.finishSecondaryWithPrimary)
assertEquals(true, rule.clearTop)
- assertEquals(0.3f, rule.splitRatio)
- assertEquals(LayoutDirection.LTR, rule.layoutDirection)
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
+ assertEquals(TEST_TAG, rule.tag)
assertEquals(filters, rule.filters)
assertEquals(123, rule.minWidthDp)
- assertEquals(456, rule.minSmallestWidthDp)
+ assertEquals(456, rule.minHeightDp)
+ assertEquals(789, rule.minSmallestWidthDp)
assertEquals(1.23f, rule.maxAspectRatioInPortrait.value)
assertEquals(4.56f, rule.maxAspectRatioInLandscape.value)
}
@@ -158,31 +223,26 @@
assertThrows(IllegalArgumentException::class.java) {
SplitPairRule.Builder(HashSet())
.setMinWidthDp(-1)
- .setMinSmallestWidthDp(456)
+ .setMinHeightDp(456)
+ .setMinSmallestWidthDp(789)
.build()
}
assertThrows(IllegalArgumentException::class.java) {
SplitPairRule.Builder(HashSet())
.setMinWidthDp(123)
+ .setMinHeightDp(-1)
+ .setMinSmallestWidthDp(789)
+ .build()
+ }
+ assertThrows(IllegalArgumentException::class.java) {
+ SplitPairRule.Builder(HashSet())
+ .setMinWidthDp(123)
+ .setMinHeightDp(456)
.setMinSmallestWidthDp(-1)
.build()
}
assertThrows(IllegalArgumentException::class.java) {
SplitPairRule.Builder(HashSet())
- .setMinWidthDp(123)
- .setMinSmallestWidthDp(456)
- .setSplitRatio(-1.0f)
- .build()
- }
- assertThrows(IllegalArgumentException::class.java) {
- SplitPairRule.Builder(HashSet())
- .setMinWidthDp(123)
- .setMinSmallestWidthDp(456)
- .setSplitRatio(1.1f)
- .build()
- }
- assertThrows(IllegalArgumentException::class.java) {
- SplitPairRule.Builder(HashSet())
.setMaxAspectRatioInPortrait(ratio(-1f))
.build()
}
@@ -202,6 +262,7 @@
// Always allow split
var rule = SplitPairRule.Builder(HashSet())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_ALLOW)
@@ -214,6 +275,7 @@
// Always disallow split in portrait
rule = SplitPairRule.Builder(HashSet())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_DISALLOW)
@@ -229,6 +291,7 @@
// Compare the aspect ratio in portrait
rule = SplitPairRule.Builder(HashSet())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ratio(1.1f))
@@ -257,6 +320,7 @@
// Always allow split
var rule = SplitPairRule.Builder(HashSet())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_ALLOW)
@@ -269,6 +333,7 @@
// Always disallow split in landscape
rule = SplitPairRule.Builder(HashSet())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_DISALLOW)
@@ -284,6 +349,7 @@
// Compare the aspect ratio in landscape
rule = SplitPairRule.Builder(HashSet())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ratio(1.1f))
@@ -313,16 +379,22 @@
.parseRules(application, R.xml.test_split_config_default_split_placeholder_rule)
assertEquals(1, rules.size)
val rule: SplitPlaceholderRule = rules.first() as SplitPlaceholderRule
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.5f))
+ .setLayoutDirection(LOCALE)
+ .setAnimationBackgroundColor(0)
+ .build()
+ assertNull(rule.tag)
assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minWidthDp)
+ assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minHeightDp)
assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minSmallestWidthDp)
assertEquals(SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT, rule.maxAspectRatioInPortrait)
assertEquals(SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT, rule.maxAspectRatioInLandscape)
- assertEquals(FINISH_ALWAYS, rule.finishPrimaryWithPlaceholder)
+ assertEquals(ALWAYS, rule.finishPrimaryWithPlaceholder)
assertEquals(false, rule.isSticky)
- assertEquals(0.5f, rule.splitRatio)
- assertEquals(LayoutDirection.LOCALE, rule.layoutDirection)
- assertTrue(rule.checkParentBounds(density, minValidWindowBounds()))
- assertFalse(rule.checkParentBounds(density, almostValidWindowBounds()))
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
+ assertTrue(rule.checkParentBounds(density, validBounds))
+ assertFalse(rule.checkParentBounds(density, invalidBounds))
}
/**
@@ -335,14 +407,44 @@
.parseRules(application, R.xml.test_split_config_custom_split_placeholder_rule)
assertEquals(1, rules.size)
val rule: SplitPlaceholderRule = rules.first() as SplitPlaceholderRule
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.1f))
+ .setLayoutDirection(RIGHT_TO_LEFT)
+ .build()
+ assertEquals("rule3", rule.tag)
assertEquals(123, rule.minWidthDp)
- assertEquals(456, rule.minSmallestWidthDp)
+ assertEquals(456, rule.minHeightDp)
+ assertEquals(789, rule.minSmallestWidthDp)
assertEquals(1.23f, rule.maxAspectRatioInPortrait.value)
assertEquals(ALWAYS_DISALLOW, rule.maxAspectRatioInLandscape)
- assertEquals(FINISH_ADJACENT, rule.finishPrimaryWithPlaceholder)
+ assertEquals(ADJACENT, rule.finishPrimaryWithPlaceholder)
assertEquals(true, rule.isSticky)
- assertEquals(0.1f, rule.splitRatio)
- assertEquals(LayoutDirection.RTL, rule.layoutDirection)
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
+ }
+
+ /**
+ * Verifies that horizontal layout are set correctly when reading [SplitPlaceholderRule]
+ * from XML.
+ */
+ @RequiresApi(Build.VERSION_CODES.M)
+ @Test
+ fun testHorizontalLayout_SplitPlaceholderRule_Xml() {
+ assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ val rules = RuleController
+ .parseRules(application, R.xml.test_split_config_split_placeholder_horizontal_layout)
+ assertEquals(1, rules.size)
+ val rule: SplitPlaceholderRule = rules.first() as SplitPlaceholderRule
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.3f))
+ .setLayoutDirection(BOTTOM_TO_TOP)
+ .setAnimationBackgroundColor(application.resources.getColor(R.color.testColor, null))
+ .build()
+ assertEquals(TEST_TAG, rule.tag)
+ assertEquals(ALWAYS, rule.finishPrimaryWithPlaceholder)
+ assertEquals(false, rule.isSticky)
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
+ assertTrue(rule.checkParentBounds(density, validBounds))
+ assertFalse(rule.checkParentBounds(density, invalidBounds))
}
/**
@@ -352,14 +454,20 @@
@Test
fun testDefaults_SplitPlaceholderRule_Builder() {
val rule = SplitPlaceholderRule.Builder(HashSet(), Intent()).build()
+ assertNull(rule.tag)
assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minWidthDp)
+ assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minHeightDp)
assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minSmallestWidthDp)
assertEquals(SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT, rule.maxAspectRatioInPortrait)
assertEquals(SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT, rule.maxAspectRatioInLandscape)
- assertEquals(FINISH_ALWAYS, rule.finishPrimaryWithPlaceholder)
+ assertEquals(ALWAYS, rule.finishPrimaryWithPlaceholder)
assertEquals(false, rule.isSticky)
- assertEquals(0.5f, rule.splitRatio)
- assertEquals(LayoutDirection.LOCALE, rule.layoutDirection)
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.5f))
+ .setLayoutDirection(LOCALE)
+ .setAnimationBackgroundColor(0)
+ .build()
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
assertTrue(rule.checkParentBounds(density, minValidWindowBounds()))
assertFalse(rule.checkParentBounds(density, almostValidWindowBounds()))
}
@@ -378,24 +486,31 @@
)
)
val intent = Intent("ACTION")
+ val expectedSplitLayout = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.ratio(0.3f))
+ .setLayoutDirection(LEFT_TO_RIGHT)
+ .setAnimationBackgroundColor(Color.GREEN)
+ .build()
val rule = SplitPlaceholderRule.Builder(filters, intent)
.setMinWidthDp(123)
- .setMinSmallestWidthDp(456)
+ .setMinHeightDp(456)
+ .setMinSmallestWidthDp(789)
.setMaxAspectRatioInPortrait(ratio(1.23f))
.setMaxAspectRatioInLandscape(ratio(4.56f))
- .setFinishPrimaryWithPlaceholder(FINISH_ADJACENT)
+ .setFinishPrimaryWithPlaceholder(ADJACENT)
.setSticky(true)
- .setSplitRatio(0.3f)
- .setLayoutDirection(LayoutDirection.LTR)
+ .setDefaultSplitAttributes(expectedSplitLayout)
+ .setTag(TEST_TAG)
.build()
- assertEquals(FINISH_ADJACENT, rule.finishPrimaryWithPlaceholder)
+ assertEquals(ADJACENT, rule.finishPrimaryWithPlaceholder)
assertEquals(true, rule.isSticky)
- assertEquals(0.3f, rule.splitRatio)
- assertEquals(LayoutDirection.LTR, rule.layoutDirection)
+ assertEquals(expectedSplitLayout, rule.defaultSplitAttributes)
assertEquals(filters, rule.filters)
assertEquals(intent, rule.placeholderIntent)
assertEquals(123, rule.minWidthDp)
- assertEquals(456, rule.minSmallestWidthDp)
+ assertEquals(456, rule.minHeightDp)
+ assertEquals(789, rule.minSmallestWidthDp)
+ assertEquals(TEST_TAG, rule.tag)
assertEquals(1.23f, rule.maxAspectRatioInPortrait.value)
assertEquals(4.56f, rule.maxAspectRatioInLandscape.value)
}
@@ -409,34 +524,30 @@
assertThrows(IllegalArgumentException::class.java) {
SplitPlaceholderRule.Builder(HashSet(), Intent())
.setMinWidthDp(-1)
- .setMinSmallestWidthDp(456)
+ .setMinHeightDp(456)
+ .setMinSmallestWidthDp(789)
.build()
}
assertThrows(IllegalArgumentException::class.java) {
SplitPlaceholderRule.Builder(HashSet(), Intent())
.setMinWidthDp(123)
+ .setMinHeightDp(-1)
+ .setMinSmallestWidthDp(789)
+ .build()
+ }
+ assertThrows(IllegalArgumentException::class.java) {
+ SplitPlaceholderRule.Builder(HashSet(), Intent())
+ .setMinWidthDp(123)
+ .setMinHeightDp(456)
.setMinSmallestWidthDp(-1)
.build()
}
assertThrows(IllegalArgumentException::class.java) {
SplitPlaceholderRule.Builder(HashSet(), Intent())
.setMinWidthDp(123)
- .setMinSmallestWidthDp(456)
- .setFinishPrimaryWithPlaceholder(FINISH_NEVER)
- .build()
- }
- assertThrows(IllegalArgumentException::class.java) {
- SplitPlaceholderRule.Builder(HashSet(), Intent())
- .setMinWidthDp(123)
- .setMinSmallestWidthDp(456)
- .setSplitRatio(-1.0f)
- .build()
- }
- assertThrows(IllegalArgumentException::class.java) {
- SplitPlaceholderRule.Builder(HashSet(), Intent())
- .setMinWidthDp(123)
- .setMinSmallestWidthDp(456)
- .setSplitRatio(1.1f)
+ .setMinHeightDp(456)
+ .setMinSmallestWidthDp(789)
+ .setFinishPrimaryWithPlaceholder(NEVER)
.build()
}
assertThrows(IllegalArgumentException::class.java) {
@@ -460,6 +571,7 @@
// Always allow split
var rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_ALLOW)
@@ -472,6 +584,7 @@
// Always disallow split in portrait
rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_DISALLOW)
@@ -487,6 +600,7 @@
// Compare the aspect ratio in portrait
rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ratio(1.1f))
@@ -515,6 +629,7 @@
// Always allow split
var rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_ALLOW)
@@ -530,6 +645,7 @@
// Always disallow split in landscape
rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ALWAYS_DISALLOW)
@@ -545,6 +661,7 @@
// Compare the aspect ratio in landscape
rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
.setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+ .setMinHeightDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
.setMaxAspectRatioInPortrait(ALWAYS_ALLOW)
.setMaxAspectRatioInLandscape(ratio(1.1f))
@@ -573,7 +690,8 @@
.parseRules(application, R.xml.test_split_config_default_activity_rule)
assertEquals(1, rules.size)
val rule: ActivityRule = rules.first() as ActivityRule
- assertEquals(false, rule.alwaysExpand)
+ assertNull(rule.tag)
+ assertFalse(rule.alwaysExpand)
}
/**
@@ -586,7 +704,22 @@
.parseRules(application, R.xml.test_split_config_custom_activity_rule)
assertEquals(1, rules.size)
val rule: ActivityRule = rules.first() as ActivityRule
- assertEquals(true, rule.alwaysExpand)
+ assertEquals("rule1", rule.tag)
+ assertTrue(rule.alwaysExpand)
+ }
+
+ /**
+ * Verifies that [ActivityRule.tag] and [ActivityRule.alwaysExpand] are set correctly when
+ * reading [ActivityRule] from XML.
+ */
+ @Test
+ fun testSetTagAndAlwaysExpand_ActivityRule_Xml() {
+ val rules = RuleController
+ .parseRules(application, R.xml.test_split_config_activity_rule_with_tag)
+ assertEquals(1, rules.size)
+ val rule: ActivityRule = rules.first() as ActivityRule
+ assertEquals(TEST_TAG, rule.tag)
+ assertTrue(rule.alwaysExpand)
}
/**
@@ -596,7 +729,7 @@
@Test
fun testDefaults_ActivityRule_Builder() {
val rule = ActivityRule.Builder(HashSet()).build()
- assertEquals(false, rule.alwaysExpand)
+ assertFalse(rule.alwaysExpand)
}
/**
@@ -613,8 +746,10 @@
)
val rule = ActivityRule.Builder(filters)
.setAlwaysExpand(true)
+ .setTag(TEST_TAG)
.build()
- assertEquals(true, rule.alwaysExpand)
+ assertTrue(rule.alwaysExpand)
+ assertEquals(TEST_TAG, rule.tag)
assertEquals(filters, rule.filters)
}
@@ -635,4 +770,60 @@
return Rect(0, 0, minValidWidthPx, minValidWidthPx)
}
+
+ @Test
+ fun testIllegalTag_XML() {
+ assertThrows(IllegalArgumentException::class.java) {
+ RuleController.parseRules(application, R.xml.test_split_config_duplicated_tag)
+ }
+ }
+
+ @Test
+ fun testReplacingRuleWithTag() {
+ var rules = RuleController
+ .parseRules(application, R.xml.test_split_config_activity_rule_with_tag)
+ assertEquals(1, rules.size)
+ var rule = rules.first()
+ assertEquals(TEST_TAG, rule.tag)
+ val staticRule = rule as ActivityRule
+ assertTrue(staticRule.alwaysExpand)
+ ruleController.setRules(rules)
+
+ val filters = HashSet<ActivityFilter>()
+ filters.add(
+ ActivityFilter(
+ ComponentName("a", "b"),
+ "ACTION"
+ )
+ )
+ val rule1 = ActivityRule.Builder(filters)
+ .setAlwaysExpand(true)
+ .setTag(TEST_TAG)
+ .build()
+ ruleController.addRule(rule1)
+
+ rules = ruleController.getRules()
+ assertEquals(1, rules.size)
+ rule = rules.first()
+ assertEquals(rule1, rule)
+
+ val intent = Intent("ACTION")
+ val rule2 = SplitPlaceholderRule.Builder(filters, intent)
+ .setMinWidthDp(123)
+ .setMinHeightDp(456)
+ .setMinSmallestWidthDp(789)
+ .setTag(TEST_TAG)
+ .build()
+
+ ruleController.addRule(rule2)
+
+ rules = ruleController.getRules()
+ assertEquals(1, rules.size)
+ rule = rules.first()
+ assertEquals(rule2, rule)
+ }
+
+ companion object {
+ const val TEST_TAG = "test"
+ }
}
\ No newline at end of file
diff --git a/window/window/src/androidTest/java/androidx/window/layout/ContextUtilsTest.kt b/window/window/src/androidTest/java/androidx/window/layout/ContextUtilsTest.kt
new file mode 100644
index 0000000..7cb4812
--- /dev/null
+++ b/window/window/src/androidTest/java/androidx/window/layout/ContextUtilsTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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
+
+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 org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+/**
+ * Instrumentation tests for [ContextUtils].
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ContextUtilsTest {
+
+ @Test
+ fun testUnwrapUiContext_noContextWrapper_activity() {
+ val context = mock(Activity::class.java)
+ assertEquals(context, unwrapUiContext(context))
+ }
+
+ @Test
+ fun testUnwrapUiContext_noContextWrapper_inputMethodService() {
+ val context = mock(InputMethodService::class.java)
+ assertEquals(context, unwrapUiContext(context))
+ }
+
+ @Test
+ fun testUnwrapUiContext_contextWrapper_null() {
+ val contextWrapper = ContextWrapper(null)
+ assertEquals(contextWrapper, unwrapUiContext(contextWrapper))
+ }
+}
\ No newline at end of file
diff --git a/window/window/src/androidTest/java/androidx/window/layout/WindowInfoTrackerImplTest.kt b/window/window/src/androidTest/java/androidx/window/layout/WindowInfoTrackerImplTest.kt
index 16dbdf6..1d86ece 100644
--- a/window/window/src/androidTest/java/androidx/window/layout/WindowInfoTrackerImplTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/layout/WindowInfoTrackerImplTest.kt
@@ -16,21 +16,24 @@
package androidx.window.layout
-import android.app.Activity
+import android.content.Context
+import android.os.Build
import androidx.core.util.Consumer
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.window.TestActivity
import androidx.window.TestConsumer
+import androidx.window.WindowTestUtils
+import androidx.window.WindowTestUtils.Companion.assumeAtLeastVendorApiLevel
import androidx.window.layout.adapter.WindowBackend
+import java.util.concurrent.Executor
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.Job
import org.junit.Rule
import org.junit.Test
-import java.util.concurrent.Executor
@OptIn(ExperimentalCoroutinesApi::class)
public class WindowInfoTrackerImplTest {
@@ -60,6 +63,25 @@
}
@Test
+ public fun testWindowLayoutFeatures_contextAsListener(): Unit = testScope.runTest {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return@runTest
+ }
+ assumeAtLeastVendorApiLevel(2)
+ val fakeBackend = FakeWindowBackend()
+ val repo = WindowInfoTrackerImpl(WindowMetricsCalculatorCompat, fakeBackend)
+ val collector = TestConsumer<WindowLayoutInfo>()
+
+ val windowContext =
+ WindowTestUtils.createOverlayWindowContext()
+ testScope.launch(Job()) {
+ repo.windowLayoutInfo(windowContext).collect(collector::accept)
+ }
+ fakeBackend.triggerSignal(WindowLayoutInfo(emptyList()))
+ collector.assertValue(WindowLayoutInfo(emptyList()))
+ }
+
+ @Test
public fun testWindowLayoutFeatures_multicasting(): Unit = testScope.runTest {
activityScenario.scenario.onActivity { testActivity ->
val windowMetricsCalculator = WindowMetricsCalculatorCompat
@@ -77,10 +99,44 @@
repo.windowLayoutInfo(testActivity).collect(collector::accept)
}
fakeBackend.triggerSignal(WindowLayoutInfo(emptyList()))
- collector.assertValues(WindowLayoutInfo(emptyList()), WindowLayoutInfo(emptyList()))
+ collector.assertValues(
+ WindowLayoutInfo(emptyList()),
+ WindowLayoutInfo(emptyList())
+ )
}
}
+ @Test
+ public fun testWindowLayoutFeatures_multicastingWithContext(): Unit = testScope.runTest {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return@runTest
+ }
+ assumeAtLeastVendorApiLevel(2)
+ val windowMetricsCalculator = WindowMetricsCalculatorCompat
+ val fakeBackend = FakeWindowBackend()
+ val repo = WindowInfoTrackerImpl(
+ windowMetricsCalculator,
+ fakeBackend
+ )
+ val collector = TestConsumer<WindowLayoutInfo>()
+ val job = Job()
+
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+
+ launch(job) {
+ repo.windowLayoutInfo(windowContext).collect(collector::accept)
+ }
+ launch(job) {
+ repo.windowLayoutInfo(windowContext).collect(collector::accept)
+ }
+
+ fakeBackend.triggerSignal(WindowLayoutInfo(emptyList()))
+ collector.assertValues(
+ WindowLayoutInfo(emptyList()),
+ WindowLayoutInfo(emptyList())
+ )
+ }
+
private class FakeWindowBackend : WindowBackend {
private class CallbackHolder(
@@ -100,7 +156,7 @@
}
override fun registerLayoutChangeCallback(
- activity: Activity,
+ context: Context,
executor: Executor,
callback: Consumer<WindowLayoutInfo>
) {
diff --git a/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackendTest.kt b/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackendTest.kt
index 1cdf333..2b80d87 100644
--- a/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackendTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackendTest.kt
@@ -16,18 +16,27 @@
package androidx.window.layout.adapter.extensions
+import androidx.window.extensions.core.util.function.Consumer as OEMConsumer
import androidx.window.extensions.layout.FoldingFeature as OEMFoldingFeature
import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
import java.util.function.Consumer as JavaConsumer
import android.annotation.SuppressLint
import android.app.Activity
+import android.content.Context
import android.graphics.Rect
import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.UiContext
import androidx.core.util.Consumer
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.window.TestActivity
import androidx.window.TestConsumer
+import androidx.window.WindowTestUtils
+import androidx.window.WindowTestUtils.Companion.assumeAtLeastVendorApiLevel
+import androidx.window.WindowTestUtils.Companion.assumeBeforeVendorApiLevel
import androidx.window.core.ConsumerAdapter
+import androidx.window.core.ExtensionsUtil
+import androidx.window.extensions.WindowExtensions
import androidx.window.extensions.layout.FoldingFeature.STATE_FLAT
import androidx.window.extensions.layout.FoldingFeature.TYPE_HINGE
import androidx.window.extensions.layout.WindowLayoutComponent
@@ -67,6 +76,7 @@
@Test
public fun testExtensionWindowBackend_delegatesToWindowLayoutComponent() {
+ assumeAtLeastVendorApiLevel(1)
val component = RequestTrackingWindowComponent()
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
@@ -80,7 +90,35 @@
}
@Test
+ public fun testExtensionWindowBackend_delegatesToWindowLayoutComponentWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+
+ val component = RequestTrackingWindowComponent()
+
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+ val windowContextConsumer = TestConsumer<WindowLayoutInfo>()
+
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, windowContextConsumer)
+ assertTrue(
+ "Expected call with Context: $windowContext",
+ component.hasAddCall(windowContext)
+ )
+ }
+
+ /**
+ * After {@link WindowExtensions#VENDOR_API_LEVEL_2} registerLayoutChangeCallback calls
+ * addWindowLayoutInfoListener(context) instead.
+ * {@link testExtensionWindowBackend_registerAtMostOnceWithContext} verifies the same behavior.
+ */
+ @Suppress("Deprecation")
+ @Test
public fun testExtensionWindowBackend_registerAtMostOnce() {
+ assumeBeforeVendorApiLevel(2)
val component = mock<WindowLayoutComponent>()
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
@@ -90,22 +128,59 @@
backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
backend.registerLayoutChangeCallback(activity, Runnable::run, mock())
- verify(component).addWindowLayoutInfoListener(eq(activity), any())
+ val consumerCaptor = argumentCaptor<JavaConsumer<OEMWindowLayoutInfo>>()
+ verify(component).addWindowLayoutInfoListener(eq(activity), consumerCaptor.capture())
+ }
+ }
+
+ @Test
+ public fun testExtensionWindowBackend_registerAtMostOnceWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+
+ val component = mock<WindowLayoutComponent>()
+
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+ val windowContextConsumer = TestConsumer<WindowLayoutInfo>()
+
+ val consumerCaptor = argumentCaptor<OEMConsumer<OEMWindowLayoutInfo>>()
+
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, windowContextConsumer)
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, mock())
+ verify(component).addWindowLayoutInfoListener(
+ eq(windowContext),
+ consumerCaptor.capture(),
+ )
+
+ activityScenario.scenario.onActivity { activity ->
+ val consumer = TestConsumer<WindowLayoutInfo>()
+ backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+ backend.registerLayoutChangeCallback(activity, Runnable::run, mock())
+ verify(component).addWindowLayoutInfoListener(
+ eq(activity as Context),
+ consumerCaptor.capture()
+ )
}
}
@Ignore // b/260647675, b/260648288
- @SuppressLint("NewApi") // java.util.function.Consumer was added in API 24 (N)
+ @Suppress("NewApi", "Deprecation") // java.util.function.Consumer was added in API 24 (N)
@Test
public fun testExtensionWindowBackend_translateValues() {
assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
val component = mock<WindowLayoutComponent>()
- whenever(component.addWindowLayoutInfoListener(any(), any()))
- .thenAnswer { invocation ->
- val consumer = invocation.getArgument(1) as JavaConsumer<OEMWindowLayoutInfo>
- consumer.accept(OEMWindowLayoutInfo(emptyList()))
- }
+ whenever(component.addWindowLayoutInfoListener(
+ any(),
+ any<JavaConsumer<OEMWindowLayoutInfo>>())
+ ).thenAnswer { invocation ->
+ val consumer = invocation.getArgument(1) as JavaConsumer<OEMWindowLayoutInfo>
+ consumer.accept(OEMWindowLayoutInfo(emptyList()))
+ }
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
activityScenario.scenario.onActivity { activity ->
@@ -116,17 +191,51 @@
}
}
- @SuppressLint("NewApi") // java.util.function.Consumer was added in API 24 (N)
@Test
- public fun testExtensionWindowBackend_infoReplayedForAdditionalListener() {
+ public fun testExtensionWindowBackend_translateValuesWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+
+ val component = FakeWindowComponent()
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+ val windowContextConsumer = TestConsumer<WindowLayoutInfo>()
+ val windowLayoutInfoFromContext = newTestOEMWindowLayoutInfo(windowContext)
+
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, windowContextConsumer)
+ component.emit(windowLayoutInfoFromContext)
+ windowContextConsumer.assertValue(
+ translate(
+ windowContext,
+ windowLayoutInfoFromContext
+ )
+ )
+
+ val consumer = TestConsumer<WindowLayoutInfo>()
+ activityScenario.scenario.onActivity { activity ->
+ val windowLayoutInfoFromActivity = newTestOEMWindowLayoutInfo(activity)
+ backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+ component.emit(newTestOEMWindowLayoutInfo(activity))
+ consumer.assertValues(listOf(translate(activity, windowLayoutInfoFromActivity)))
+ }
+ }
+
+ @Suppress("NewApi", "Deprecation") // java.util.function.Consumer was added in API 24 (N)
+ @Test
+ fun testExtensionWindowBackend_infoReplayedForAdditionalListener() {
+ assumeBeforeVendorApiLevel(2)
assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
val component = mock<WindowLayoutComponent>()
- whenever(component.addWindowLayoutInfoListener(any(), any()))
- .thenAnswer { invocation ->
- val consumer = invocation.getArgument(1) as JavaConsumer<OEMWindowLayoutInfo>
- consumer.accept(OEMWindowLayoutInfo(emptyList()))
- }
+ whenever(component.addWindowLayoutInfoListener(
+ any(),
+ any<JavaConsumer<OEMWindowLayoutInfo>>())
+ ).thenAnswer { invocation ->
+ val consumer = invocation.getArgument(1) as JavaConsumer<OEMWindowLayoutInfo>
+ consumer.accept(OEMWindowLayoutInfo(emptyList()))
+ }
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
activityScenario.scenario.onActivity { activity ->
@@ -139,7 +248,48 @@
}
@Test
- public fun testExtensionWindowBackend_removeMatchingCallback() {
+ public fun testExtensionWindowBackend_infoReplayedForAdditionalListenerWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+
+ val component = mock<WindowLayoutComponent>()
+ whenever(component.addWindowLayoutInfoListener(
+ any(),
+ any<OEMConsumer<OEMWindowLayoutInfo>>())
+ ).thenAnswer { invocation ->
+ val consumer = invocation.getArgument(1) as OEMConsumer<OEMWindowLayoutInfo>
+ consumer.accept(OEMWindowLayoutInfo(emptyList()))
+ }
+ whenever(component.addWindowLayoutInfoListener(
+ any(),
+ any<OEMConsumer<OEMWindowLayoutInfo>>())
+ ).thenAnswer { invocation ->
+ val consumer = invocation.getArgument(1) as OEMConsumer<OEMWindowLayoutInfo>
+ consumer.accept(OEMWindowLayoutInfo(emptyList()))
+ }
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+
+ activityScenario.scenario.onActivity { activity ->
+ val consumer = TestConsumer<WindowLayoutInfo>()
+ backend.registerLayoutChangeCallback(activity, Runnable::run, mock())
+ backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+
+ consumer.assertValue(WindowLayoutInfo(emptyList()))
+ }
+
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+ val windowContextConsumer = TestConsumer<WindowLayoutInfo>()
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, windowContextConsumer)
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, mock())
+ windowContextConsumer.assertValue(WindowLayoutInfo(emptyList()))
+ }
+
+ @Suppress("Deprecation")
+ @Test
+ fun testExtensionWindowBackend_removeMatchingCallback() {
+ assumeBeforeVendorApiLevel(2)
val component = mock<WindowLayoutComponent>()
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
@@ -156,8 +306,10 @@
}
}
+ @Suppress("Deprecation")
@Test
- public fun testExtensionWindowBackend_removesMultipleCallback() {
+ fun testExtensionWindowBackend_removesMultipleCallback() {
+ assumeBeforeVendorApiLevel(2)
val component = mock<WindowLayoutComponent>()
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
@@ -177,8 +329,116 @@
}
}
+ /**
+ * Verifies context and consumer registration can be registered with using either
+ * addWindowLayoutInfoListener(context) or addWindowLayoutInfoListener(activity),
+ * but all registration are cleaned up by removeWindowLayoutInfoListener().
+ * Note: addWindowLayoutInfoListener(context) is added in
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}.
+ */
@Test
- public fun testExtensionWindowBackend_reRegisterCallback() {
+ fun testExtensionWindowBackend_removeMatchingCallbackWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ // createWindowContext is available after R.
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+
+ val component = mock<WindowLayoutComponent>()
+
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+
+ activityScenario.scenario.onActivity { activity ->
+ val consumer = TestConsumer<WindowLayoutInfo>()
+ backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+ backend.unregisterLayoutChangeCallback(consumer)
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+ val windowContextConsumer = TestConsumer<WindowLayoutInfo>()
+ backend.registerLayoutChangeCallback(
+ windowContext,
+ Runnable::run,
+ windowContextConsumer
+ )
+ backend.unregisterLayoutChangeCallback(windowContextConsumer)
+
+ val consumerCaptor = argumentCaptor<OEMConsumer<OEMWindowLayoutInfo>>()
+ verify(component).addWindowLayoutInfoListener(
+ eq(activity as Context),
+ consumerCaptor.capture()
+ )
+ verify(component).removeWindowLayoutInfoListener(
+ consumerCaptor.firstValue
+ )
+
+ verify(component).addWindowLayoutInfoListener(
+ eq(windowContext),
+ consumerCaptor.capture()
+ )
+ verify(component).removeWindowLayoutInfoListener(
+ consumerCaptor.lastValue
+ )
+ }
+ }
+
+ @Test
+ fun testExtensionWindowBackend_removeMultipleCallbackWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ // createWindowContext is available after R.
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+
+ val component = mock<WindowLayoutComponent>()
+
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+
+ activityScenario.scenario.onActivity { activity ->
+ val consumer = TestConsumer<WindowLayoutInfo>()
+ val consumer2 = TestConsumer<WindowLayoutInfo>()
+ backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+ backend.registerLayoutChangeCallback(activity, Runnable::run, consumer2)
+ backend.unregisterLayoutChangeCallback(consumer)
+ backend.unregisterLayoutChangeCallback(consumer2)
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+ val windowContextConsumer = TestConsumer<WindowLayoutInfo>()
+ val windowContextConsumer2 = TestConsumer<WindowLayoutInfo>()
+ backend.registerLayoutChangeCallback(
+ windowContext,
+ Runnable::run,
+ windowContextConsumer
+ )
+ backend.registerLayoutChangeCallback(
+ windowContext,
+ Runnable::run,
+ windowContextConsumer2
+ )
+ backend.unregisterLayoutChangeCallback(windowContextConsumer)
+ backend.unregisterLayoutChangeCallback(windowContextConsumer2)
+
+ val consumerCaptor = argumentCaptor<OEMConsumer<OEMWindowLayoutInfo>>()
+ verify(component).addWindowLayoutInfoListener(
+ eq(activity as Context),
+ consumerCaptor.capture()
+ )
+ verify(component).removeWindowLayoutInfoListener(
+ consumerCaptor.firstValue
+ )
+
+ verify(component).addWindowLayoutInfoListener(
+ eq(windowContext),
+ consumerCaptor.capture()
+ )
+ verify(component).removeWindowLayoutInfoListener(
+ consumerCaptor.lastValue
+ )
+ assertFalse(backend.hasRegisteredListeners())
+ }
+ }
+
+ @Suppress("Deprecation")
+ @Test
+ fun testExtensionWindowBackend_reRegisterCallback() {
+ assumeBeforeVendorApiLevel(2)
val component = mock<WindowLayoutComponent>()
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
@@ -198,8 +458,58 @@
}
}
+ /**
+ * Verifies that a [WindowLayoutInfo] is published to the consumer upon each registration.
+ * Note: addWindowLayoutInfoListener(context) is added in
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @Test
+ fun testExtensionWindowBackend_reRegisterCallbackWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+
+ val component = mock<WindowLayoutComponent>()
+
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+ val windowContextConsumer = TestConsumer<WindowLayoutInfo>()
+
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, windowContextConsumer)
+ backend.unregisterLayoutChangeCallback(windowContextConsumer)
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, windowContextConsumer)
+
+ val consumerCaptor = argumentCaptor<OEMConsumer<OEMWindowLayoutInfo>>()
+ verify(component, times(2)).addWindowLayoutInfoListener(
+ eq(windowContext),
+ consumerCaptor.capture()
+ )
+ verify(component).removeWindowLayoutInfoListener(
+ consumerCaptor.firstValue
+ )
+
+ activityScenario.scenario.onActivity { activity ->
+ val consumer = TestConsumer<WindowLayoutInfo>()
+ backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+ backend.unregisterLayoutChangeCallback(consumer)
+ backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+
+ verify(component, times(2))
+ .addWindowLayoutInfoListener(
+ eq(activity as Context),
+ consumerCaptor.capture()
+ )
+ verify(component).removeWindowLayoutInfoListener(
+ consumerCaptor.firstValue
+ )
+ }
+ }
+
@Test
public fun testRegisterLayoutChangeCallback_clearListeners() {
+ assumeBeforeVendorApiLevel(2)
activityScenario.scenario.onActivity { activity ->
val component = FakeWindowComponent()
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
@@ -213,12 +523,12 @@
{ obj: Runnable -> obj.run() },
firstConsumer
)
+
backend.registerLayoutChangeCallback(
activity,
{ obj: Runnable -> obj.run() },
secondConsumer
)
-
assertEquals("Expected one registration for same Activity", 1, component.consumers.size)
// Check unregistering the layout change callback
backend.unregisterLayoutChangeCallback(firstConsumer)
@@ -227,8 +537,50 @@
}
}
+ /**
+ * Verifies that both [Activity] and [UiContext] can be independently registered as listeners to
+ * [WindowLayoutInfo].
+ * Note: addWindowLayoutInfoListener(context) is added in
+ * {@link WindowExtensions#VENDOR_API_LEVEL_2}
+ */
+ @Test
+ public fun testRegisterLayoutChangeCallback_clearListenersWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+
+ activityScenario.scenario.onActivity { activity ->
+ val component = FakeWindowComponent()
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+
+ // Check registering the layout change callback
+ val firstConsumer = mock<Consumer<WindowLayoutInfo>>()
+ val secondConsumer = mock<Consumer<WindowLayoutInfo>>()
+ val thirdConsumer = mock<Consumer<WindowLayoutInfo>>()
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+
+ backend.registerLayoutChangeCallback(activity, Runnable::run, firstConsumer)
+ backend.registerLayoutChangeCallback(activity, Runnable::run, secondConsumer)
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, thirdConsumer)
+
+ assertEquals(
+ "Expected one registration for same Activity",
+ 2 /* expected */,
+ component.oemConsumers.size
+ )
+ // Check unregistering the layout change callback
+ backend.unregisterLayoutChangeCallback(firstConsumer)
+ backend.unregisterLayoutChangeCallback(secondConsumer)
+ backend.unregisterLayoutChangeCallback(thirdConsumer)
+ assertTrue("Expected all listeners to be removed", component.oemConsumers.isEmpty())
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
@Test
public fun testLayoutChangeCallback_emitNewValue() {
+ assumeBeforeVendorApiLevel(2)
activityScenario.scenario.onActivity { activity ->
val component = FakeWindowComponent()
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
@@ -245,7 +597,31 @@
}
@Test
+ public fun testExtensionWindowBackend_emitNewValueWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+
+ val component = FakeWindowComponent()
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+
+ // Check that callbacks from the extension are propagated for WindowContext.
+ val consumer = mock<Consumer<WindowLayoutInfo>>()
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+ backend.registerLayoutChangeCallback(
+ windowContext, Runnable::run, consumer
+ )
+ val windowLayoutInfo = newTestOEMWindowLayoutInfo(windowContext)
+
+ component.emit(windowLayoutInfo)
+ verify(consumer).accept(translate(windowContext, windowLayoutInfo))
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ @Test
public fun testWindowLayoutInfo_updatesOnSubsequentRegistration() {
+ assumeAtLeastVendorApiLevel(1)
activityScenario.scenario.onActivity { activity ->
val component = FakeWindowComponent()
val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
@@ -266,8 +642,36 @@
}
}
- internal companion object {
+ @Test
+ public fun testWindowLayoutInfo_updatesOnSubsequentRegistrationWithContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return
+ }
+ assumeAtLeastVendorApiLevel(2)
+ val component = FakeWindowComponent()
+ val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+ val consumer = TestConsumer<WindowLayoutInfo>()
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+
+ val oemWindowLayoutInfo = newTestOEMWindowLayoutInfo(windowContext)
+
+ val expected = listOf(
+ translate(windowContext, oemWindowLayoutInfo),
+ translate(windowContext, oemWindowLayoutInfo)
+ )
+
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, consumer)
+ component.emit(newTestOEMWindowLayoutInfo(windowContext))
+ backend.unregisterLayoutChangeCallback(consumer)
+
+ backend.registerLayoutChangeCallback(windowContext, Runnable::run, consumer)
+ component.emit(newTestOEMWindowLayoutInfo(windowContext))
+ backend.unregisterLayoutChangeCallback(consumer)
+ consumer.assertValues(expected)
+ }
+
+ internal companion object {
private fun newTestOEMWindowLayoutInfo(activity: Activity): OEMWindowLayoutInfo {
val bounds = WindowMetricsCalculatorCompat.computeCurrentWindowMetrics(activity).bounds
val featureBounds = Rect(0, bounds.centerY(), bounds.width(), bounds.centerY())
@@ -275,6 +679,20 @@
val displayFeatures = listOf(feature)
return OEMWindowLayoutInfo(displayFeatures)
}
+
+ /**
+ * Creates an empty OEMWindowLayoutInfo. Note that before R context needs to be an
+ * [Activity]. After R Context can be an [Activity] or a [UiContext] created with
+ * [Context#createWindowContext] or [InputMethodService].
+ */
+ @RequiresApi(Build.VERSION_CODES.R)
+ private fun newTestOEMWindowLayoutInfo(@UiContext context: Context): OEMWindowLayoutInfo {
+ val bounds = WindowMetricsCalculatorCompat.computeCurrentWindowMetrics(context).bounds
+ val featureBounds = Rect(0, bounds.centerY(), bounds.width(), bounds.centerY())
+ val feature = OEMFoldingFeature(featureBounds, TYPE_HINGE, STATE_FLAT)
+ val displayFeatures = listOf(feature)
+ return OEMWindowLayoutInfo(displayFeatures)
+ }
}
private class RequestTrackingWindowComponent : WindowLayoutComponent {
@@ -288,19 +706,27 @@
records.add(AddCall(activity))
}
+ override fun addWindowLayoutInfoListener(
+ context: Context,
+ consumer: OEMConsumer<OEMWindowLayoutInfo>,
+ ) {
+ records.add(AddCall(context))
+ }
+
override fun removeWindowLayoutInfoListener(consumer: JavaConsumer<OEMWindowLayoutInfo>) {
}
- class AddCall(val activity: Activity)
+ class AddCall(val context: Context)
- fun hasAddCall(activity: Activity): Boolean {
- return records.any { addRecord -> addRecord.activity == activity }
+ fun hasAddCall(context: Context): Boolean {
+ return records.any { addRecord -> addRecord.context == context }
}
}
private class FakeWindowComponent : WindowLayoutComponent {
val consumers = mutableListOf<JavaConsumer<OEMWindowLayoutInfo>>()
+ val oemConsumers = mutableListOf<OEMConsumer<OEMWindowLayoutInfo>>()
override fun addWindowLayoutInfoListener(
activity: Activity,
@@ -309,13 +735,30 @@
consumers.add(consumer)
}
+ override fun addWindowLayoutInfoListener(
+ context: Context,
+ consumer: OEMConsumer<OEMWindowLayoutInfo>
+ ) {
+ oemConsumers.add(consumer)
+ }
+
override fun removeWindowLayoutInfoListener(consumer: JavaConsumer<OEMWindowLayoutInfo>) {
consumers.remove(consumer)
}
+ override fun removeWindowLayoutInfoListener(
+ consumer: OEMConsumer<OEMWindowLayoutInfo>
+ ) {
+ oemConsumers.remove(consumer)
+ }
+
@SuppressLint("NewApi")
fun emit(info: OEMWindowLayoutInfo) {
- consumers.forEach { it.accept(info) }
+ if (ExtensionsUtil.safeVendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+ consumers.forEach { it.accept(info) }
+ } else {
+ oemConsumers.forEach { it.accept(info) }
+ }
}
}
}
diff --git a/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionsWindowLayoutInfoAdapterTest.kt b/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionsWindowLayoutInfoAdapterTest.kt
index f1d8f53..376cf74 100644
--- a/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionsWindowLayoutInfoAdapterTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionsWindowLayoutInfoAdapterTest.kt
@@ -16,26 +16,30 @@
package androidx.window.layout.adapter.extensions
+import androidx.window.extensions.layout.FoldingFeature as OEMFoldingFeature
+import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
import android.graphics.Rect
+import android.os.Build
+import androidx.annotation.RequiresApi
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.window.TestActivity
+import androidx.window.WindowTestUtils
import androidx.window.core.Bounds
import androidx.window.extensions.layout.FoldingFeature.STATE_HALF_OPENED
import androidx.window.extensions.layout.FoldingFeature.TYPE_HINGE
import androidx.window.layout.FoldingFeature.State.Companion.HALF_OPENED
+import androidx.window.layout.HardwareFoldingFeature
import androidx.window.layout.HardwareFoldingFeature.Type.Companion.HINGE
import androidx.window.layout.TestFoldingFeatureUtil.invalidNonZeroFoldBounds
+import androidx.window.layout.WindowLayoutInfo
import androidx.window.layout.WindowMetricsCalculatorCompat.computeCurrentWindowMetrics
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
-import androidx.window.extensions.layout.FoldingFeature as OEMFoldingFeature
-import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
-import androidx.window.layout.HardwareFoldingFeature
-import androidx.window.layout.WindowLayoutInfo
class ExtensionsWindowLayoutInfoAdapterTest {
@@ -46,12 +50,13 @@
@Test
fun testTranslate_foldingFeature() {
activityScenario.scenario.onActivity { activity ->
- val bounds = computeCurrentWindowMetrics(activity).bounds
+ val windowMetrics = computeCurrentWindowMetrics(activity)
+ val bounds = windowMetrics.bounds
val featureBounds = Rect(0, bounds.centerY(), bounds.width(), bounds.centerY())
val oemFeature = OEMFoldingFeature(featureBounds, TYPE_HINGE, STATE_HALF_OPENED)
val expected = HardwareFoldingFeature(Bounds(featureBounds), HINGE, HALF_OPENED)
- val actual = ExtensionsWindowLayoutInfoAdapter.translate(activity, oemFeature)
+ val actual = ExtensionsWindowLayoutInfoAdapter.translate(windowMetrics, oemFeature)
assertEquals(expected, actual)
}
@@ -74,14 +79,34 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.S)
@Test
- fun testTranslate_foldingFeature_invalidType() {
+ fun testTranslate_windowLayoutInfoFromContext() {
+ assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
activityScenario.scenario.onActivity { activity ->
val bounds = computeCurrentWindowMetrics(activity).bounds
val featureBounds = Rect(0, bounds.centerY(), bounds.width(), bounds.centerY())
+ val oemFeature = OEMFoldingFeature(featureBounds, TYPE_HINGE, STATE_HALF_OPENED)
+ val oemInfo = OEMWindowLayoutInfo(listOf(oemFeature))
+ val localFeature = HardwareFoldingFeature(Bounds(featureBounds), HINGE, HALF_OPENED)
+ val expected = WindowLayoutInfo(listOf(localFeature))
+
+ val windowContext = WindowTestUtils.createOverlayWindowContext()
+
+ val fromContext = ExtensionsWindowLayoutInfoAdapter.translate(windowContext, oemInfo)
+ assertEquals(expected, fromContext)
+ }
+ }
+
+ @Test
+ fun testTranslate_foldingFeature_invalidType() {
+ activityScenario.scenario.onActivity { activity ->
+ val windowMetrics = computeCurrentWindowMetrics(activity)
+ val bounds = windowMetrics.bounds
+ val featureBounds = Rect(0, bounds.centerY(), bounds.width(), bounds.centerY())
val oemFeature = OEMFoldingFeature(featureBounds, -1, STATE_HALF_OPENED)
- val actual = ExtensionsWindowLayoutInfoAdapter.translate(activity, oemFeature)
+ val actual = ExtensionsWindowLayoutInfoAdapter.translate(windowMetrics, oemFeature)
assertNull(actual)
}
@@ -90,11 +115,12 @@
@Test
fun testTranslate_foldingFeature_invalidState() {
activityScenario.scenario.onActivity { activity ->
- val bounds = computeCurrentWindowMetrics(activity).bounds
+ val windowMetrics = computeCurrentWindowMetrics(activity)
+ val bounds = windowMetrics.bounds
val featureBounds = Rect(0, bounds.centerY(), bounds.width(), bounds.centerY())
val oemFeature = OEMFoldingFeature(featureBounds, TYPE_HINGE, -1)
- val actual = ExtensionsWindowLayoutInfoAdapter.translate(activity, oemFeature)
+ val actual = ExtensionsWindowLayoutInfoAdapter.translate(windowMetrics, oemFeature)
assertNull(actual)
}
@@ -103,7 +129,8 @@
@Test
fun testTranslate_foldingFeature_invalidBounds() {
activityScenario.scenario.onActivity { activity ->
- val windowBounds = computeCurrentWindowMetrics(activity).bounds
+ val windowMetrics = computeCurrentWindowMetrics(activity)
+ val windowBounds = windowMetrics.bounds
val source = invalidNonZeroFoldBounds(windowBounds)
.map { featureBounds ->
@@ -111,7 +138,7 @@
}
val invalidFeatures = source.mapNotNull { feature ->
- ExtensionsWindowLayoutInfoAdapter.translate(activity, feature)
+ ExtensionsWindowLayoutInfoAdapter.translate(windowMetrics, feature)
}
assertTrue(source.isNotEmpty())
@@ -121,4 +148,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/window/window-samples/src/main/res/values/colors.xml b/window/window/src/androidTest/res/values/colors.xml
similarity index 62%
copy from window/window-samples/src/main/res/values/colors.xml
copy to window/window/src/androidTest/res/values/colors.xml
index 41a72b2..939eec6 100644
--- a/window/window-samples/src/main/res/values/colors.xml
+++ b/window/window/src/androidTest/res/values/colors.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2020 The Android Open Source Project
+ 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.
@@ -14,14 +14,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<resources>
- <color name="colorPrimary">#6200EE</color>
- <color name="colorPrimaryDark">#3700B3</color>
- <color name="colorAccent">#03DAC5</color>
-
- <color name="colorFeatureFold">#7700FF00</color>
-
- <color name="colorSplitContentBackground">#3B6BDB4C</color>
- <color name="colorSplitControlsBackground">#475ABFF3</color>
-</resources>
+ <color name="testColor">#00FF00</color>
+</resources>
\ No newline at end of file
diff --git a/window/window-samples/src/main/res/values/colors.xml b/window/window/src/androidTest/res/xml/test_split_config_activity_rule_with_tag.xml
similarity index 60%
copy from window/window-samples/src/main/res/values/colors.xml
copy to window/window/src/androidTest/res/xml/test_split_config_activity_rule_with_tag.xml
index 41a72b2..d7e0025 100644
--- a/window/window-samples/src/main/res/values/colors.xml
+++ b/window/window/src/androidTest/res/xml/test_split_config_activity_rule_with_tag.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2020 The Android Open Source Project
+ 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.
@@ -14,14 +14,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<resources>
- <color name="colorPrimary">#6200EE</color>
- <color name="colorPrimaryDark">#3700B3</color>
- <color name="colorAccent">#03DAC5</color>
-
- <color name="colorFeatureFold">#7700FF00</color>
-
- <color name="colorSplitContentBackground">#3B6BDB4C</color>
- <color name="colorSplitControlsBackground">#475ABFF3</color>
-</resources>
+<resources
+ xmlns:window="http://schemas.android.com/apk/res-auto">
+ <ActivityRule
+ window:tag="test"
+ window:alwaysExpand="true">
+ <ActivityFilter
+ window:activityName="androidx.window.sample.embedding.SplitActivityList"/>
+ </ActivityRule>
+</resources>
\ No newline at end of file
diff --git a/window/window/src/androidTest/res/xml/test_split_config_custom_activity_rule.xml b/window/window/src/androidTest/res/xml/test_split_config_custom_activity_rule.xml
index 63e54b6..f651baf 100644
--- a/window/window/src/androidTest/res/xml/test_split_config_custom_activity_rule.xml
+++ b/window/window/src/androidTest/res/xml/test_split_config_custom_activity_rule.xml
@@ -17,6 +17,7 @@
<resources
xmlns:window="http://schemas.android.com/apk/res-auto">
<ActivityRule
+ window:tag="rule1"
window:alwaysExpand="true">
<ActivityFilter
window:activityName="androidx.window.sample.embedding.SplitActivityList"/>
diff --git a/window/window/src/androidTest/res/xml/test_split_config_custom_split_pair_rule.xml b/window/window/src/androidTest/res/xml/test_split_config_custom_split_pair_rule.xml
index 128eeaa..face30c 100644
--- a/window/window/src/androidTest/res/xml/test_split_config_custom_split_pair_rule.xml
+++ b/window/window/src/androidTest/res/xml/test_split_config_custom_split_pair_rule.xml
@@ -17,9 +17,11 @@
<resources
xmlns:window="http://schemas.android.com/apk/res-auto">
<SplitPairRule
+ window:tag="rule2"
window:splitRatio="0.1"
window:splitMinWidthDp="123"
- window:splitMinSmallestWidthDp="456"
+ window:splitMinHeightDp="456"
+ window:splitMinSmallestWidthDp="789"
window:splitMaxAspectRatioInPortrait="1.23"
window:splitMaxAspectRatioInLandscape="alwaysDisallow"
window:splitLayoutDirection="rtl"
diff --git a/window/window/src/androidTest/res/xml/test_split_config_custom_split_placeholder_rule.xml b/window/window/src/androidTest/res/xml/test_split_config_custom_split_placeholder_rule.xml
index 3a0716e..273c855 100644
--- a/window/window/src/androidTest/res/xml/test_split_config_custom_split_placeholder_rule.xml
+++ b/window/window/src/androidTest/res/xml/test_split_config_custom_split_placeholder_rule.xml
@@ -17,9 +17,11 @@
<resources
xmlns:window="http://schemas.android.com/apk/res-auto">
<SplitPlaceholderRule
+ window:tag="rule3"
window:splitRatio="0.1"
window:splitMinWidthDp="123"
- window:splitMinSmallestWidthDp="456"
+ window:splitMinHeightDp="456"
+ window:splitMinSmallestWidthDp="789"
window:splitMaxAspectRatioInPortrait="1.23"
window:splitMaxAspectRatioInLandscape="alwaysDisallow"
window:splitLayoutDirection="rtl"
diff --git a/window/window/src/androidTest/res/xml/test_split_config_default_activity_rule.xml b/window/window/src/androidTest/res/xml/test_split_config_default_activity_rule.xml
index 3ae66aa..e47b93e 100644
--- a/window/window/src/androidTest/res/xml/test_split_config_default_activity_rule.xml
+++ b/window/window/src/androidTest/res/xml/test_split_config_default_activity_rule.xml
@@ -18,6 +18,6 @@
xmlns:window="http://schemas.android.com/apk/res-auto">
<ActivityRule>
<ActivityFilter
- window:activityName="androidx.window.sample.embedding.SplitActivityList"/>
+ window:activityName="SplitActivityList"/>
</ActivityRule>
</resources>
\ No newline at end of file
diff --git a/window/window/src/androidTest/res/xml/test_split_config_default_split_placeholder_rule.xml b/window/window/src/androidTest/res/xml/test_split_config_default_split_placeholder_rule.xml
index ffbc6ca..e67acb3 100644
--- a/window/window/src/androidTest/res/xml/test_split_config_default_split_placeholder_rule.xml
+++ b/window/window/src/androidTest/res/xml/test_split_config_default_split_placeholder_rule.xml
@@ -19,6 +19,6 @@
<SplitPlaceholderRule
window:placeholderActivityName="C">
<ActivityFilter
- window:activityName="androidx.window.sample.embedding.SplitActivityList"/>
+ window:activityName="SplitActivityList"/>
</SplitPlaceholderRule>
</resources>
\ No newline at end of file
diff --git a/window/window/src/androidTest/res/xml/test_split_config_duplicated_tag.xml b/window/window/src/androidTest/res/xml/test_split_config_duplicated_tag.xml
new file mode 100644
index 0000000..aba63db
--- /dev/null
+++ b/window/window/src/androidTest/res/xml/test_split_config_duplicated_tag.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<resources
+ xmlns:window="http://schemas.android.com/apk/res-auto">
+ <ActivityRule
+ window:tag="test">
+ <ActivityFilter
+ window:activityName="A"/>
+ </ActivityRule>
+ <SplitPairRule
+ window:tag="test">
+ <SplitPairFilter
+ window:primaryActivityName="A"
+ window:secondaryActivityName="B"/>
+ </SplitPairRule>
+</resources>
\ No newline at end of file
diff --git a/window/window/src/androidTest/res/xml/test_split_config_split_pair_rule_horizontal_layout.xml b/window/window/src/androidTest/res/xml/test_split_config_split_pair_rule_horizontal_layout.xml
new file mode 100644
index 0000000..5e75a12
--- /dev/null
+++ b/window/window/src/androidTest/res/xml/test_split_config_split_pair_rule_horizontal_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<resources
+ xmlns:window="http://schemas.android.com/apk/res-auto">
+ <SplitPairRule
+ window:splitRatio="0.3"
+ window:tag="test"
+ window:splitMinWidthDp="0"
+ window:splitMinHeightDp="600"
+ window:splitMinSmallestWidthDp="0"
+ window:splitLayoutDirection="topToBottom"
+ window:animationBackgroundColor="#0000FF">
+ <SplitPairFilter
+ window:primaryActivityName="A"
+ window:secondaryActivityName="B"/>
+ </SplitPairRule>
+</resources>
\ No newline at end of file
diff --git a/window/window/src/androidTest/res/xml/test_split_config_split_placeholder_horizontal_layout.xml b/window/window/src/androidTest/res/xml/test_split_config_split_placeholder_horizontal_layout.xml
new file mode 100644
index 0000000..df0871f
--- /dev/null
+++ b/window/window/src/androidTest/res/xml/test_split_config_split_placeholder_horizontal_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<resources
+ xmlns:window="http://schemas.android.com/apk/res-auto">
+ <SplitPlaceholderRule
+ window:tag="test"
+ window:placeholderActivityName="C"
+ window:splitRatio="0.3"
+ window:splitMinWidthDp="0"
+ window:splitMinHeightDp="600"
+ window:splitMinSmallestWidthDp="0"
+ window:splitLayoutDirection="bottomToTop"
+ window:animationBackgroundColor="@color/testColor">
+ <ActivityFilter
+ window:activityName="androidx.window.sample.embedding.SplitActivityList"/>
+ </SplitPlaceholderRule>
+</resources>
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/WindowProperties.kt b/window/window/src/main/java/androidx/window/WindowProperties.kt
index 226430b..156652f 100644
--- a/window/window/src/main/java/androidx/window/WindowProperties.kt
+++ b/window/window/src/main/java/androidx/window/WindowProperties.kt
@@ -30,9 +30,10 @@
*
* If `true`, the system is permitted to override the app's windowing
* behavior and implement activity embedding split rules, such as displaying
- * activities side by side. A system override informs the app that the
- * activity embedding APIs are disabled so the app will not provide its own
- * activity embedding rules, which would conflict with the system's rules.
+ * activities adjacent to each other. A system override informs the app that
+ * the activity embedding APIs are disabled so the app will not provide its
+ * own activity embedding rules, which would conflict with the system's
+ * rules.
*
* If `false`, the system is not permitted to override the windowing
* behavior of the app. Set the property to `false` if the app provides its
@@ -62,4 +63,28 @@
*/
const val PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE =
"android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
+
+ /**
+ * Application level
+ * [PackageManager][android.content.pm.PackageManager.Property] tag
+ * that an app must specify to inform the system that the app is ActivityEmbedding
+ * split feature enabled. In other words, the ActivityEmbedding splits feature cannot be
+ * used if the app has no property set.
+ *
+ * With this property, the system could provide custom behaviors for the apps that
+ * have ActivityEmbedding split feature enabled. For example, the fixed-portrait orientation
+ * requests of the activities could be ignored by the system in order to provide seamless
+ * ActivityEmbedding split experiences while holding the large-screen devices in landscape mode.
+ *
+ * **Syntax:**
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
+ * android:value="true|false"/>
+ * </application>
+ * </pre>
+ */
+ const val PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED =
+ "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/core/ConsumerAdapter.kt b/window/window/src/main/java/androidx/window/core/ConsumerAdapter.kt
index e81e16e..af51cd0 100644
--- a/window/window/src/main/java/androidx/window/core/ConsumerAdapter.kt
+++ b/window/window/src/main/java/androidx/window/core/ConsumerAdapter.kt
@@ -18,6 +18,7 @@
import android.annotation.SuppressLint
import android.app.Activity
+import android.content.Context
import androidx.annotation.CheckResult
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
@@ -84,6 +85,61 @@
}
}
+ @CheckResult
+ fun <T : Any> createSubscriptionNoActivity(
+ obj: Any,
+ clazz: KClass<T>,
+ addMethodName: String,
+ removeMethodName: String,
+ consumer: (T) -> Unit
+ ): Subscription {
+ val javaConsumer = buildConsumer(clazz, consumer)
+ obj.javaClass.getMethod(addMethodName, unsafeConsumerClass())
+ .invoke(obj, javaConsumer)
+ val removeMethod = obj.javaClass.getMethod(removeMethodName, unsafeConsumerClass())
+ return object : Subscription {
+ override fun dispose() {
+ removeMethod.invoke(obj, javaConsumer)
+ }
+ }
+ }
+
+ @CheckResult
+ fun <T : Any> createSubscription(
+ obj: Any,
+ clazz: KClass<T>,
+ addMethodName: String,
+ removeMethodName: String,
+ context: Context,
+ consumer: (T) -> Unit
+ ): Subscription {
+ val javaConsumer = buildConsumer(clazz, consumer)
+ obj.javaClass.getMethod(addMethodName, Context::class.java, unsafeConsumerClass())
+ .invoke(obj, context, javaConsumer)
+ val removeMethod = obj.javaClass.getMethod(removeMethodName, unsafeConsumerClass())
+ return object : Subscription {
+ override fun dispose() {
+ removeMethod.invoke(obj, javaConsumer)
+ }
+ }
+ }
+
+ /**
+ * Similar to {@link #createSubscription} but without needing to provide
+ * a {@code removeMethodName} due to it being handled on the extensions side
+ */
+ fun <T : Any> createConsumer(
+ obj: Any,
+ clazz: KClass<T>,
+ addMethodName: String,
+ activity: Activity,
+ consumer: (T) -> Unit
+ ) {
+ val javaConsumer = buildConsumer(clazz, consumer)
+ obj.javaClass.getMethod(addMethodName, Activity::class.java, unsafeConsumerClass())
+ .invoke(obj, activity, javaConsumer)
+ }
+
private class ConsumerHandler<T : Any>(
private val clazz: KClass<T>,
private val consumer: (T) -> Unit
diff --git a/window/window/src/main/java/androidx/window/embedding/ActivityRule.kt b/window/window/src/main/java/androidx/window/embedding/ActivityRule.kt
index fbaebcc..c231818 100644
--- a/window/window/src/main/java/androidx/window/embedding/ActivityRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ActivityRule.kt
@@ -21,6 +21,7 @@
* [SplitPairRule].
*/
class ActivityRule internal constructor(
+ tag: String?,
/**
* Filters used to choose when to apply this rule. The rule will be applied if any one of the
* provided filters matches.
@@ -32,7 +33,7 @@
* activity that blocks all user interactions, such as a warning dialog.
*/
val alwaysExpand: Boolean = false
-) : EmbeddingRule() {
+) : EmbeddingRule(tag) {
/**
* Builder for [ActivityRule].
@@ -40,8 +41,9 @@
* @param filters See [ActivityRule.filters].
*/
class Builder(
- private val filters: Set<ActivityFilter>
+ private val filters: Set<ActivityFilter>,
) {
+ private var tag: String? = null
private var alwaysExpand: Boolean = false
/**
@@ -53,7 +55,11 @@
fun setAlwaysExpand(alwaysExpand: Boolean): Builder =
apply { this.alwaysExpand = alwaysExpand }
- fun build() = ActivityRule(filters, alwaysExpand)
+ /** @see ActivityRule.tag */
+ fun setTag(tag: String): Builder =
+ apply { this.tag = tag }
+
+ fun build() = ActivityRule(tag, filters, alwaysExpand)
}
/**
@@ -61,16 +67,14 @@
* @see filters
*/
internal operator fun plus(filter: ActivityFilter): ActivityRule {
- return ActivityRule(
- filters + filter,
- alwaysExpand
- )
+ return ActivityRule(tag, filters + filter, alwaysExpand)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ActivityRule) return false
+ if (!super.equals(other)) return false
if (filters != other.filters) return false
if (alwaysExpand != other.alwaysExpand) return false
@@ -78,8 +82,16 @@
}
override fun hashCode(): Int {
- var result = filters.hashCode()
+ var result = super.hashCode()
+ result = 31 * result + filters.hashCode()
result = 31 * result + alwaysExpand.hashCode()
return result
}
-}
\ No newline at end of file
+
+ override fun toString(): String {
+ return "ActivityRule:{" +
+ "tag={$tag}," +
+ "filters={$filters}, " +
+ "alwaysExpand={$alwaysExpand}}"
+ }
+}
diff --git a/window/window/src/main/java/androidx/window/embedding/ActivityStack.kt b/window/window/src/main/java/androidx/window/embedding/ActivityStack.kt
index 4d693c7..310ce17 100644
--- a/window/window/src/main/java/androidx/window/embedding/ActivityStack.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ActivityStack.kt
@@ -63,11 +63,9 @@
return result
}
- override fun toString(): String {
- return buildString {
- append("ActivityStack{")
- append("activitiesInProcess=$activitiesInProcess")
- append("isEmpty=$isEmpty}")
- }
- }
-}
\ No newline at end of file
+ override fun toString(): String =
+ "ActivityStack{" +
+ "activitiesInProcess=$activitiesInProcess" +
+ ", isEmpty=$isEmpty" +
+ "}"
+}
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
index 1700d8d..c96432f 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
@@ -16,9 +16,13 @@
package androidx.window.embedding
+import android.util.Pair as AndroidPair
import androidx.window.extensions.embedding.ActivityRule as OEMActivityRule
import androidx.window.extensions.embedding.ActivityRule.Builder as ActivityRuleBuilder
import androidx.window.extensions.embedding.EmbeddingRule as OEMEmbeddingRule
+import androidx.window.extensions.embedding.SplitAttributes as OEMSplitAttributes
+import androidx.window.extensions.embedding.SplitAttributes.SplitType as OEMSplitType
+import androidx.window.extensions.embedding.SplitAttributesCalculatorParams as OEMSplitAttributesCalculatorParams
import androidx.window.extensions.embedding.SplitInfo as OEMSplitInfo
import androidx.window.extensions.embedding.SplitPairRule as OEMSplitPairRule
import androidx.window.extensions.embedding.SplitPairRule.Builder as SplitPairRuleBuilder
@@ -28,9 +32,24 @@
import android.app.Activity
import android.content.Context
import android.content.Intent
+import android.util.LayoutDirection
import android.view.WindowMetrics
+import androidx.window.core.ExtensionsUtil
import androidx.window.core.PredicateAdapter
-import androidx.window.extensions.WindowExtensionsProvider
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.BOTTOM_TO_TOP
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LEFT_TO_RIGHT
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LOCALE
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.RIGHT_TO_LEFT
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.TOP_TO_BOTTOM
+import androidx.window.embedding.SplitAttributes.SplitType
+import androidx.window.extensions.WindowExtensions
+import androidx.window.extensions.core.util.function.Function
+import androidx.window.extensions.core.util.function.Predicate
+import androidx.window.extensions.embedding.SplitPairRule.FINISH_ADJACENT
+import androidx.window.extensions.embedding.SplitPairRule.FINISH_ALWAYS
+import androidx.window.extensions.embedding.SplitPairRule.FINISH_NEVER
+import androidx.window.layout.WindowMetricsCalculator
+import androidx.window.layout.adapter.extensions.ExtensionsWindowLayoutInfoAdapter
/**
* Adapter class that translates data classes between Extension and Jetpack interfaces.
@@ -38,9 +57,11 @@
internal class EmbeddingAdapter(
private val predicateAdapter: PredicateAdapter
) {
+ private val vendorApiLevel = ExtensionsUtil.safeVendorApiLevel
+ private val vendorApiLevel1Impl = VendorApiLevel1Impl(predicateAdapter)
fun translate(splitInfoList: List<OEMSplitInfo>): List<SplitInfo> {
- return splitInfoList.map(::translate)
+ return splitInfoList.map(this::translate)
}
private fun translate(splitInfo: OEMSplitInfo): SplitInfo {
@@ -66,125 +87,240 @@
secondaryActivityStack.activities,
isSecondaryStackEmpty
)
- return SplitInfo(primaryFragment, secondaryFragment, splitInfo.splitRatio)
- }
- @SuppressLint("ClassVerificationFailure", "NewApi")
- private fun translateActivityPairPredicates(splitPairFilters: Set<SplitPairFilter>): Any {
- return predicateAdapter.buildPairPredicate(
- Activity::class,
- Activity::class
- ) { first: Activity, second: Activity ->
- splitPairFilters.any { filter -> filter.matchesActivityPair(first, second) }
+ val splitAttributes = if (vendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2) {
+ translate(splitInfo.splitAttributes)
+ } else {
+ vendorApiLevel1Impl.getSplitAttributesCompat(splitInfo)
}
+ return SplitInfo(primaryFragment, secondaryFragment, splitAttributes)
}
- @SuppressLint("ClassVerificationFailure", "NewApi")
- private fun translateActivityIntentPredicates(splitPairFilters: Set<SplitPairFilter>): Any {
- return predicateAdapter.buildPairPredicate(
- Activity::class,
- Intent::class
- ) { first, second ->
- splitPairFilters.any { filter -> filter.matchesActivityIntentPair(first, second) }
+ private fun translate(splitAttributes: OEMSplitAttributes): SplitAttributes =
+ SplitAttributes.Builder()
+ .setSplitType(translate(splitAttributes.splitType))
+ .setLayoutDirection(
+ when (val layoutDirection = splitAttributes.layoutDirection) {
+ OEMSplitAttributes.LayoutDirection.LEFT_TO_RIGHT -> LEFT_TO_RIGHT
+ OEMSplitAttributes.LayoutDirection.RIGHT_TO_LEFT -> RIGHT_TO_LEFT
+ OEMSplitAttributes.LayoutDirection.LOCALE -> LOCALE
+ OEMSplitAttributes.LayoutDirection.TOP_TO_BOTTOM -> TOP_TO_BOTTOM
+ OEMSplitAttributes.LayoutDirection.BOTTOM_TO_TOP -> BOTTOM_TO_TOP
+ else -> throw IllegalArgumentException(
+ "Unknown layout direction: $layoutDirection"
+ )
+ }
+ )
+ .setAnimationBackgroundColor(splitAttributes.animationBackgroundColor)
+ .build()
+
+ private fun translate(splitType: OEMSplitType): SplitType =
+ when (splitType) {
+ is OEMSplitType.RatioSplitType -> translate(splitType)
+ is OEMSplitType.ExpandContainersSplitType -> SplitType.expandContainers()
+ is OEMSplitType.HingeSplitType -> translate(splitType)
+ else -> throw IllegalArgumentException("Unsupported split type: $splitType")
}
- }
- @SuppressLint("ClassVerificationFailure", "NewApi")
- private fun translateParentMetricsPredicate(context: Context, splitRule: SplitRule): Any {
- return predicateAdapter.buildPredicate(WindowMetrics::class) { windowMetrics ->
- splitRule.checkParentMetrics(context, windowMetrics)
+ private fun translate(hinge: OEMSplitType.HingeSplitType): SplitType.HingeSplitType =
+ SplitType.splitByHinge(
+ when (val splitType = hinge.fallbackSplitType) {
+ is OEMSplitType.ExpandContainersSplitType -> SplitType.expandContainers()
+ is OEMSplitType.RatioSplitType -> translate(splitType)
+ else -> throw IllegalArgumentException("Unsupported split type: $splitType")
+ }
+ )
+
+ private fun translate(splitRatio: OEMSplitType.RatioSplitType): SplitType.RatioSplitType =
+ SplitType.ratio(splitRatio.ratio)
+
+ fun translateSplitAttributesCalculator(
+ calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
+ ): Function<OEMSplitAttributesCalculatorParams, OEMSplitAttributes> = Function { oemParams ->
+ translateSplitAttributes(calculator.invoke(translate(oemParams)))
}
+
+ @SuppressLint("NewApi")
+ fun translate(
+ params: OEMSplitAttributesCalculatorParams
+ ): SplitAttributesCalculatorParams = let {
+ val taskWindowMetrics = params.parentWindowMetrics
+ val taskConfiguration = params.parentConfiguration
+ val windowLayoutInfo = params.parentWindowLayoutInfo
+ val defaultSplitAttributes = params.defaultSplitAttributes
+ val areDefaultConstraintsSatisfied = params.areDefaultConstraintsSatisfied()
+ val splitRuleTag = params.splitRuleTag
+ val windowMetrics = WindowMetricsCalculator.translateWindowMetrics(taskWindowMetrics)
+
+ SplitAttributesCalculatorParams(
+ windowMetrics,
+ taskConfiguration,
+ ExtensionsWindowLayoutInfoAdapter.translate(windowMetrics, windowLayoutInfo),
+ translate(defaultSplitAttributes),
+ areDefaultConstraintsSatisfied,
+ splitRuleTag,
+ )
}
- @SuppressLint("ClassVerificationFailure", "NewApi")
- private fun translateActivityPredicates(activityFilters: Set<ActivityFilter>): Any {
- return predicateAdapter.buildPredicate(Activity::class) { activity ->
- activityFilters.any { filter -> filter.matchesActivity(activity) }
- }
- }
-
- @SuppressLint("ClassVerificationFailure", "NewApi")
- private fun translateIntentPredicates(activityFilters: Set<ActivityFilter>): Any {
- return predicateAdapter.buildPredicate(Intent::class) { intent ->
- activityFilters.any { filter -> filter.matchesIntent(intent) }
- }
- }
-
- @SuppressLint("WrongConstant") // Converting from Jetpack to Extensions constants
private fun translateSplitPairRule(
context: Context,
rule: SplitPairRule,
predicateClass: Class<*>
): OEMSplitPairRule {
- val builder = SplitPairRuleBuilder::class.java.getConstructor(
- predicateClass,
- predicateClass,
- predicateClass
- ).newInstance(
- translateActivityPairPredicates(rule.filters),
- translateActivityIntentPredicates(rule.filters),
- translateParentMetricsPredicate(context, rule)
- )
- .setSplitRatio(rule.splitRatio)
- .setLayoutDirection(rule.layoutDirection)
- .setShouldClearTop(rule.clearTop)
- .setFinishPrimaryWithSecondary(rule.finishPrimaryWithSecondary)
- .setFinishSecondaryWithPrimary(rule.finishSecondaryWithPrimary)
- return builder.build()
+ if (vendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+ return vendorApiLevel1Impl.translateSplitPairRuleCompat(context, rule, predicateClass)
+ } else {
+ val activitiesPairPredicate =
+ Predicate<AndroidPair<Activity, Activity>> { activitiesPair ->
+ rule.filters.any { filter ->
+ filter.matchesActivityPair(activitiesPair.first, activitiesPair.second)
+ }
+ }
+ val activityIntentPredicate =
+ Predicate<AndroidPair<Activity, Intent>> { activityIntentPair ->
+ rule.filters.any { filter ->
+ filter.matchesActivityIntentPair(
+ activityIntentPair.first,
+ activityIntentPair.second
+ )
+ }
+ }
+ val windowMetricsPredicate = Predicate<WindowMetrics> { windowMetrics ->
+ rule.checkParentMetrics(context, windowMetrics)
+ }
+ val tag = rule.tag
+ val builder = SplitPairRuleBuilder(
+ activitiesPairPredicate,
+ activityIntentPredicate,
+ windowMetricsPredicate,
+ )
+ .setDefaultSplitAttributes(translateSplitAttributes(rule.defaultSplitAttributes))
+ .setFinishPrimaryWithSecondary(
+ translateFinishBehavior(rule.finishPrimaryWithSecondary)
+ ).setFinishSecondaryWithPrimary(
+ translateFinishBehavior(rule.finishSecondaryWithPrimary)
+ ).setShouldClearTop(rule.clearTop)
+
+ if (tag != null) {
+ builder.setTag(tag)
+ }
+ return builder.build()
+ }
}
- @SuppressLint("WrongConstant") // Converting from Jetpack to Extensions constants
+ private fun translateSplitAttributes(splitAttributes: SplitAttributes): OEMSplitAttributes {
+ require(vendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2)
+ // To workaround the "unused" error in ktlint. It is necessary to translate SplitAttributes
+ // from WM Jetpack version to WM extension version.
+ return androidx.window.extensions.embedding.SplitAttributes.Builder()
+ .setSplitType(translateSplitType(splitAttributes.splitType))
+ .setLayoutDirection(
+ when (splitAttributes.layoutDirection) {
+ LOCALE -> OEMSplitAttributes.LayoutDirection.LOCALE
+ LEFT_TO_RIGHT -> OEMSplitAttributes.LayoutDirection.LEFT_TO_RIGHT
+ RIGHT_TO_LEFT -> OEMSplitAttributes.LayoutDirection.RIGHT_TO_LEFT
+ TOP_TO_BOTTOM -> OEMSplitAttributes.LayoutDirection.TOP_TO_BOTTOM
+ BOTTOM_TO_TOP -> OEMSplitAttributes.LayoutDirection.BOTTOM_TO_TOP
+ else -> throw IllegalArgumentException("Unsupported layoutDirection:" +
+ "$splitAttributes.layoutDirection"
+ )
+ }
+ )
+ .setAnimationBackgroundColor(splitAttributes.animationBackgroundColor)
+ .build()
+ }
+
+ private fun translateSplitType(splitType: SplitType): OEMSplitType {
+ require(vendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2)
+ return when (splitType) {
+ is SplitType.HingeSplitType -> translateHinge(splitType)
+ is SplitType.ExpandContainersSplitType -> OEMSplitType.ExpandContainersSplitType()
+ is SplitType.RatioSplitType -> translateRatio(splitType)
+ else -> throw IllegalArgumentException("Unsupported splitType: $splitType")
+ }
+ }
+
+ private fun translateHinge(hinge: SplitType.HingeSplitType): OEMSplitType.HingeSplitType =
+ OEMSplitType.HingeSplitType(
+ when (val splitType = hinge.fallbackSplitType) {
+ is SplitType.ExpandContainersSplitType -> OEMSplitType.ExpandContainersSplitType()
+ is SplitType.RatioSplitType -> translateRatio(splitType)
+ else -> throw IllegalArgumentException("Unsupported splitType: $splitType")
+ }
+ )
+
+ private fun translateRatio(splitRatio: SplitType.RatioSplitType): OEMSplitType.RatioSplitType =
+ OEMSplitType.RatioSplitType(splitRatio.ratio)
+
private fun translateSplitPlaceholderRule(
context: Context,
rule: SplitPlaceholderRule,
predicateClass: Class<*>
): OEMSplitPlaceholderRule {
- val builder = SplitPlaceholderRuleBuilder::class.java.getConstructor(
- Intent::class.java,
- predicateClass,
- predicateClass,
- predicateClass
- ).newInstance(
- rule.placeholderIntent,
- translateActivityPredicates(rule.filters),
- translateIntentPredicates(rule.filters),
- translateParentMetricsPredicate(context, rule)
- )
- .setSplitRatio(rule.splitRatio)
- .setLayoutDirection(rule.layoutDirection)
- .setSticky(rule.isSticky)
- .safeSetFinishPrimaryWithPlaceholder(rule.finishPrimaryWithPlaceholder)
- return builder.build()
- }
-
- @Suppress("DEPRECATION")
- // setFinishPrimaryWithSecondary is to be deprecated but we want to make a safe fallback
- // behavior here for compatibility reason.
- // Suppressing deprecation warning to prevent breaking build.
- private fun SplitPlaceholderRuleBuilder.safeSetFinishPrimaryWithPlaceholder(
- behavior: @SplitPlaceholderRule.SplitPlaceholderFinishBehavior Int
- ): SplitPlaceholderRuleBuilder {
- var extensionApiLevel: Int = WindowExtensionsProvider.getWindowExtensions().vendorApiLevel
- return if (extensionApiLevel >= 2) {
- setFinishPrimaryWithPlaceholder(behavior)
+ if (vendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+ return vendorApiLevel1Impl.translateSplitPlaceholderRuleCompat(
+ context,
+ rule,
+ predicateClass
+ )
} else {
- setFinishPrimaryWithSecondary(behavior)
+ val activityPredicate = Predicate<Activity> { activity ->
+ rule.filters.any { filter -> filter.matchesActivity(activity) }
+ }
+ val intentPredicate = Predicate<Intent> { intent ->
+ rule.filters.any { filter -> filter.matchesIntent(intent) }
+ }
+ val windowMetricsPredicate = Predicate<WindowMetrics> { windowMetrics ->
+ rule.checkParentMetrics(context, windowMetrics)
+ }
+ val tag = rule.tag
+ val builder = SplitPlaceholderRuleBuilder(
+ rule.placeholderIntent,
+ activityPredicate,
+ intentPredicate,
+ windowMetricsPredicate
+ )
+ .setSticky(rule.isSticky)
+ .setDefaultSplitAttributes(translateSplitAttributes(rule.defaultSplitAttributes))
+ .setFinishPrimaryWithPlaceholder(
+ translateFinishBehavior(rule.finishPrimaryWithPlaceholder)
+ )
+ if (tag != null) {
+ builder.setTag(tag)
+ }
+ return builder.build()
}
}
+ fun translateFinishBehavior(behavior: SplitRule.FinishBehavior): Int =
+ when (behavior) {
+ SplitRule.FinishBehavior.NEVER -> FINISH_NEVER
+ SplitRule.FinishBehavior.ALWAYS -> FINISH_ALWAYS
+ SplitRule.FinishBehavior.ADJACENT -> FINISH_ADJACENT
+ else -> throw IllegalArgumentException("Unknown finish behavior:$behavior")
+ }
+
private fun translateActivityRule(
rule: ActivityRule,
predicateClass: Class<*>
): OEMActivityRule {
- return ActivityRuleBuilder::class.java.getConstructor(
- predicateClass,
- predicateClass
- ).newInstance(
- translateActivityPredicates(rule.filters),
- translateIntentPredicates(rule.filters)
- )
- .setShouldAlwaysExpand(rule.alwaysExpand)
- .build()
+ if (vendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+ return vendorApiLevel1Impl.translateActivityRuleCompat(rule, predicateClass)
+ } else {
+ val activityPredicate = Predicate<Activity> { activity ->
+ rule.filters.any { filter -> filter.matchesActivity(activity) }
+ }
+ val intentPredicate = Predicate<Intent> { intent ->
+ rule.filters.any { filter -> filter.matchesIntent(intent) }
+ }
+ val builder = ActivityRuleBuilder(activityPredicate, intentPredicate)
+ .setShouldAlwaysExpand(rule.alwaysExpand)
+ val tag = rule.tag
+ if (tag != null) {
+ builder.setTag(tag)
+ }
+ return builder.build()
+ }
}
fun translate(context: Context, rules: Set<EmbeddingRule>): Set<OEMEmbeddingRule> {
@@ -199,4 +335,164 @@
}
}.toSet()
}
+
+ /**
+ * Provides backward compatibility for Window extensions with
+ * [WindowExtensions.VENDOR_API_LEVEL_1]
+ * @see WindowExtensions.getVendorApiLevel
+ */
+ // Suppress deprecation because this object is to provide backward compatibility.
+ @Suppress("DEPRECATION")
+ private inner class VendorApiLevel1Impl(val predicateAdapter: PredicateAdapter) {
+ /**
+ * Obtains [SplitAttributes] from [OEMSplitInfo] with [WindowExtensions.VENDOR_API_LEVEL_1]
+ */
+ fun getSplitAttributesCompat(splitInfo: OEMSplitInfo): SplitAttributes =
+ SplitAttributes.Builder()
+ .setSplitType(SplitType.buildSplitTypeFromValue(splitInfo.splitRatio))
+ .setLayoutDirection(LOCALE)
+ .build()
+
+ fun translateActivityRuleCompat(
+ rule: ActivityRule,
+ predicateClass: Class<*>
+ ): OEMActivityRule = ActivityRuleBuilder::class.java.getConstructor(
+ predicateClass,
+ predicateClass
+ ).newInstance(
+ translateActivityPredicates(rule.filters),
+ translateIntentPredicates(rule.filters)
+ )
+ .setShouldAlwaysExpand(rule.alwaysExpand)
+ .build()
+
+ fun translateSplitPlaceholderRuleCompat(
+ context: Context,
+ rule: SplitPlaceholderRule,
+ predicateClass: Class<*>
+ ): OEMSplitPlaceholderRule = SplitPlaceholderRuleBuilder::class.java.getConstructor(
+ Intent::class.java,
+ predicateClass,
+ predicateClass,
+ predicateClass
+ ).newInstance(
+ rule.placeholderIntent,
+ translateActivityPredicates(rule.filters),
+ translateIntentPredicates(rule.filters),
+ translateParentMetricsPredicate(context, rule)
+ )
+ .setSticky(rule.isSticky)
+ .setFinishPrimaryWithSecondary(
+ translateFinishBehavior(rule.finishPrimaryWithPlaceholder)
+ ).setDefaultSplitAttributesCompat(rule.defaultSplitAttributes)
+ .build()
+
+ private fun SplitPlaceholderRuleBuilder.setDefaultSplitAttributesCompat(
+ defaultAttrs: SplitAttributes,
+ ): SplitPlaceholderRuleBuilder = apply {
+ val (splitRatio, layoutDirection) = translateSplitAttributesCompatInternal(defaultAttrs)
+ // #setDefaultAttributes or SplitAttributes ctr weren't supported.
+ setSplitRatio(splitRatio)
+ setLayoutDirection(layoutDirection)
+ }
+
+ fun translateSplitPairRuleCompat(
+ context: Context,
+ rule: SplitPairRule,
+ predicateClass: Class<*>
+ ): OEMSplitPairRule = SplitPairRuleBuilder::class.java.getConstructor(
+ predicateClass,
+ predicateClass,
+ predicateClass,
+ ).newInstance(
+ translateActivityPairPredicates(rule.filters),
+ translateActivityIntentPredicates(rule.filters),
+ translateParentMetricsPredicate(context, rule)
+ )
+ .setDefaultSplitAttributesCompat(rule.defaultSplitAttributes)
+ .setShouldClearTop(rule.clearTop)
+ .setFinishPrimaryWithSecondary(
+ translateFinishBehavior(rule.finishPrimaryWithSecondary)
+ ).setFinishSecondaryWithPrimary(
+ translateFinishBehavior(rule.finishSecondaryWithPrimary)
+ ).build()
+
+ @SuppressLint("ClassVerificationFailure", "NewApi")
+ private fun translateActivityPairPredicates(splitPairFilters: Set<SplitPairFilter>): Any {
+ return predicateAdapter.buildPairPredicate(
+ Activity::class,
+ Activity::class
+ ) { first: Activity, second: Activity ->
+ splitPairFilters.any { filter -> filter.matchesActivityPair(first, second) }
+ }
+ }
+
+ @SuppressLint("ClassVerificationFailure", "NewApi")
+ private fun translateActivityIntentPredicates(splitPairFilters: Set<SplitPairFilter>): Any {
+ return predicateAdapter.buildPairPredicate(
+ Activity::class,
+ Intent::class
+ ) { first, second ->
+ splitPairFilters.any { filter -> filter.matchesActivityIntentPair(first, second) }
+ }
+ }
+
+ private fun SplitPairRuleBuilder.setDefaultSplitAttributesCompat(
+ defaultAttrs: SplitAttributes,
+ ): SplitPairRuleBuilder = apply {
+ val (splitRatio, layoutDirection) = translateSplitAttributesCompatInternal(defaultAttrs)
+ setSplitRatio(splitRatio)
+ setLayoutDirection(layoutDirection)
+ }
+
+ private fun translateSplitAttributesCompatInternal(
+ attrs: SplitAttributes
+ ): Pair<Float, Int> = // Use a (Float, Integer) pair since SplitAttributes weren't supported
+ if (!isSplitAttributesSupported(attrs)) {
+ // Fallback to expand the secondary container if the SplitAttributes are not
+ // supported.
+ Pair(0.0f, LayoutDirection.LOCALE)
+ } else {
+ Pair(
+ attrs.splitType.value,
+ when (attrs.layoutDirection) {
+ // Legacy LayoutDirection uses LayoutDirection constants in framework APIs.
+ LOCALE -> LayoutDirection.LOCALE
+ LEFT_TO_RIGHT -> LayoutDirection.LTR
+ RIGHT_TO_LEFT -> LayoutDirection.RTL
+ else -> throw IllegalStateException("Unsupported layout direction must be" +
+ " covered in @isSplitAttributesSupported!")
+ }
+ )
+ }
+
+ /**
+ * Returns `true` if `attrs` is compatible with [WindowExtensions.VENDOR_API_LEVEL_1] and
+ * doesn't use the new features introduced in [WindowExtensions.VENDOR_API_LEVEL_2] or
+ * higher.
+ */
+ private fun isSplitAttributesSupported(attrs: SplitAttributes) =
+ attrs.splitType is SplitType.RatioSplitType &&
+ attrs.layoutDirection in arrayOf(LEFT_TO_RIGHT, RIGHT_TO_LEFT, LOCALE)
+
+ @SuppressLint("ClassVerificationFailure", "NewApi")
+ private fun translateActivityPredicates(activityFilters: Set<ActivityFilter>): Any {
+ return predicateAdapter.buildPredicate(Activity::class) { activity ->
+ activityFilters.any { filter -> filter.matchesActivity(activity) }
+ }
+ }
+
+ @SuppressLint("ClassVerificationFailure", "NewApi")
+ private fun translateIntentPredicates(activityFilters: Set<ActivityFilter>): Any {
+ return predicateAdapter.buildPredicate(Intent::class) { intent ->
+ activityFilters.any { filter -> filter.matchesIntent(intent) }
+ }
+ }
+
+ @SuppressLint("ClassVerificationFailure", "NewApi")
+ private fun translateParentMetricsPredicate(context: Context, splitRule: SplitRule): Any =
+ predicateAdapter.buildPredicate(WindowMetrics::class) { windowMetrics ->
+ splitRule.checkParentMetrics(context, windowMetrics)
+ }
+ }
}
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingAspectRatio.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingAspectRatio.kt
index 609e357..e09b9a4 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingAspectRatio.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingAspectRatio.kt
@@ -19,7 +19,7 @@
import androidx.annotation.FloatRange
/**
- * The aspect ratio of the parent window bounds to allow embedding with the rule.
+ * The aspect ratio of the parent window bound to allow embedding with the rule.
*
* @see SplitRule.maxAspectRatioInPortrait
* @see SplitRule.maxAspectRatioInLandscape
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
index 539e69d..77ed5ef 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
@@ -43,4 +43,12 @@
fun isSplitSupported(): Boolean
fun isActivityEmbedded(activity: Activity): Boolean
+
+ fun setSplitAttributesCalculator(
+ calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
+ )
+
+ fun clearSplitAttributesCalculator()
+
+ fun isSplitAttributesCalculatorSupported(): Boolean
}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
index ea701a1..0109ecf 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
@@ -21,8 +21,12 @@
import android.content.Context
import android.util.Log
import androidx.window.core.ConsumerAdapter
+import androidx.window.core.ExtensionsUtil
import androidx.window.embedding.EmbeddingInterfaceCompat.EmbeddingCallbackInterface
+import androidx.window.extensions.WindowExtensions
+import androidx.window.extensions.WindowExtensions.VENDOR_API_LEVEL_2
import androidx.window.extensions.WindowExtensionsProvider
+import androidx.window.extensions.core.util.function.Consumer
import androidx.window.extensions.embedding.ActivityEmbeddingComponent
import java.lang.reflect.Proxy
@@ -38,18 +42,40 @@
) : EmbeddingInterfaceCompat {
override fun setRules(rules: Set<EmbeddingRule>) {
+ var hasSplitRule = false
+ for (rule in rules) {
+ if (rule is SplitRule) {
+ hasSplitRule = true
+ break
+ }
+ }
+ if (hasSplitRule && !SplitController.getInstance(applicationContext).isSplitSupported()) {
+ Log.e(
+ TAG, "Cannot set SplitRule because ActivityEmbedding Split is not supported" +
+ " or PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED is not set."
+ )
+ return
+ }
+
val r = adapter.translate(applicationContext, rules)
embeddingExtension.setEmbeddingRules(r)
}
override fun setEmbeddingCallback(embeddingCallback: EmbeddingCallbackInterface) {
- consumerAdapter.addConsumer(
- embeddingExtension,
- List::class,
- "setSplitInfoCallback"
- ) { values ->
- val splitInfoList = values.filterIsInstance<OEMSplitInfo>()
- embeddingCallback.onSplitInfoChanged(adapter.translate(splitInfoList))
+ if (ExtensionsUtil.safeVendorApiLevel < VENDOR_API_LEVEL_2) {
+ consumerAdapter.addConsumer(
+ embeddingExtension,
+ List::class,
+ "setSplitInfoCallback"
+ ) { values ->
+ val splitInfoList = values.filterIsInstance<OEMSplitInfo>()
+ embeddingCallback.onSplitInfoChanged(adapter.translate(splitInfoList))
+ }
+ } else {
+ val callback = Consumer<List<OEMSplitInfo>> { splitInfoList ->
+ embeddingCallback.onSplitInfoChanged(adapter.translate(splitInfoList))
+ }
+ embeddingExtension.setSplitInfoCallback(callback)
}
}
@@ -57,6 +83,29 @@
return embeddingExtension.isActivityEmbedded(activity)
}
+ override fun setSplitAttributesCalculator(
+ calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
+ ) {
+ if (!isSplitAttributesCalculatorSupported()) {
+ throw UnsupportedOperationException("#setSplitAttributesCalculator is not supported " +
+ "on the device.")
+ }
+ return embeddingExtension.setSplitAttributesCalculator(
+ adapter.translateSplitAttributesCalculator(calculator)
+ )
+ }
+
+ override fun clearSplitAttributesCalculator() {
+ if (!isSplitAttributesCalculatorSupported()) {
+ throw UnsupportedOperationException("#clearSplitAttributesCalculator is not " +
+ "supported on the device.")
+ }
+ return embeddingExtension.clearSplitAttributesCalculator()
+ }
+
+ override fun isSplitAttributesCalculatorSupported(): Boolean =
+ ExtensionsUtil.safeVendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2
+
companion object {
const val DEBUG = true
private const val TAG = "EmbeddingCompat"
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
index ff0f50f..8c74aec 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
@@ -34,4 +34,12 @@
}
fun isActivityEmbedded(activity: Activity): Boolean
+
+ fun setSplitAttributesCalculator(
+ calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
+ )
+
+ fun clearSplitAttributesCalculator()
+
+ fun isSplitAttributesCalculatorSupported(): Boolean
}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingRule.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingRule.kt
index e3ff7dd..987360f 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingRule.kt
@@ -20,4 +20,26 @@
* Base abstract class for activity embedding presentation rules, such as [SplitPairRule] and
* [ActivityRule]. Allows grouping different rule types together when updating.
*/
-abstract class EmbeddingRule internal constructor()
+abstract class EmbeddingRule internal constructor(
+ /**
+ * A unique string to identify this [EmbeddingRule], which defaults to `null`.
+ * The suggested usage is to set the tag in the corresponding rule builder to be able to
+ * differentiate between different rules in the callbacks. For example, it can be used to
+ * compute the right [SplitAttributes] for the right split rule in callback set in
+ * [SplitController.setSplitAttributesCalculator].
+ *
+ * @see androidx.window.embedding.RuleController.addRule
+ */
+ val tag: String?
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is EmbeddingRule) return false
+
+ return tag == other.tag
+ }
+
+ override fun hashCode(): Int {
+ return tag.hashCode()
+ }
+}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
index 7fb48ffc..666b488 100644
--- a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
@@ -21,13 +21,13 @@
import android.util.Log
import androidx.annotation.GuardedBy
import androidx.annotation.VisibleForTesting
+import androidx.collection.ArraySet
import androidx.core.util.Consumer
import androidx.window.core.ConsumerAdapter
import androidx.window.core.ExtensionsUtil
import androidx.window.core.PredicateAdapter
import androidx.window.embedding.EmbeddingInterfaceCompat.EmbeddingCallbackInterface
import java.util.concurrent.CopyOnWriteArrayList
-import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.Executor
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
@@ -116,30 +116,109 @@
}
}
- private val rules: CopyOnWriteArraySet<EmbeddingRule> =
- CopyOnWriteArraySet<EmbeddingRule>()
+ @GuardedBy("globalLock")
+ private val ruleTracker = RuleTracker()
+ @GuardedBy("globalLock")
override fun getRules(): Set<EmbeddingRule> {
- return rules
+ globalLock.withLock { return ruleTracker.splitRules }
}
+ @GuardedBy("globalLock")
override fun setRules(rules: Set<EmbeddingRule>) {
- this.rules.clear()
- this.rules.addAll(rules)
- embeddingExtension?.setRules(this.rules)
- }
-
- override fun addRule(rule: EmbeddingRule) {
- if (!rules.contains(rule)) {
- rules.add(rule)
- embeddingExtension?.setRules(rules)
+ globalLock.withLock {
+ ruleTracker.setRules(rules)
+ embeddingExtension?.setRules(getRules())
}
}
+ @GuardedBy("globalLock")
+ override fun addRule(rule: EmbeddingRule) {
+ globalLock.withLock {
+ if (rule !in ruleTracker) {
+ ruleTracker.addOrUpdateRule(rule)
+ embeddingExtension?.setRules(getRules())
+ }
+ }
+ }
+
+ @GuardedBy("globalLock")
override fun removeRule(rule: EmbeddingRule) {
- if (rules.contains(rule)) {
- rules.remove(rule)
- embeddingExtension?.setRules(rules)
+ globalLock.withLock {
+ if (rule in ruleTracker) {
+ ruleTracker.removeRule(rule)
+ embeddingExtension?.setRules(getRules())
+ }
+ }
+ }
+
+ /**
+ * A helper class to manage the registered [tags][EmbeddingRule.tag] and [rules][EmbeddingRule]
+ * It supports:
+ * - Add a set of [rules][EmbeddingRule] and verify if there's duplicated [EmbeddingRule.tag]
+ * if needed.
+ * - Clears all registered [rules][EmbeddingRule]
+ * - Add a runtime [rule][EmbeddingRule] or update an existing [rule][EmbeddingRule] by
+ * [tag][EmbeddingRule.tag] if the tag has been registered.
+ * - Remove a runtime [rule][EmbeddingRule]
+ */
+ private class RuleTracker {
+ val splitRules = ArraySet<EmbeddingRule>()
+ private val tagRuleMap = HashMap<String, EmbeddingRule>()
+
+ fun setRules(rules: Set<EmbeddingRule>) {
+ clearRules()
+ rules.forEach { rule -> addOrUpdateRule(rule, throwOnDuplicateTag = true) }
+ }
+
+ fun clearRules() {
+ splitRules.clear()
+ tagRuleMap.clear()
+ }
+
+ /**
+ * Adds a rule to [RuleTracker] or update an existing rule if the [tag][EmbeddingRule.tag]
+ * has been registered and `throwOnDuplicateTag` is `false`
+ * @throws IllegalArgumentException if `throwOnDuplicateTag` is `true` and the
+ * [tag][EmbeddingRule.tag] has been registered.
+ */
+ fun addOrUpdateRule(rule: EmbeddingRule, throwOnDuplicateTag: Boolean = false) {
+ if (rule in splitRules) {
+ return
+ }
+ val tag = rule.tag
+ if (tag == null) {
+ splitRules.add(rule)
+ } else if (tagRuleMap.containsKey(tag)) {
+ if (throwOnDuplicateTag) {
+ throw IllegalArgumentException("Duplicated tag: $tag. Tag must be unique " +
+ "among all registered rules")
+ } else {
+ // Update the rule if throwOnDuplicateTag = false
+ val oldRule = tagRuleMap[tag]
+ splitRules.remove(oldRule)
+ tagRuleMap[tag] = rule
+ splitRules.add(rule)
+ }
+ } else {
+ tagRuleMap[tag] = rule
+ splitRules.add(rule)
+ }
+ }
+
+ fun removeRule(rule: EmbeddingRule) {
+ if (rule !in splitRules) {
+ return
+ }
+ splitRules.remove(rule)
+ val tag = rule.tag
+ if (tag != null) {
+ tagRuleMap.remove(rule.tag)
+ }
+ }
+
+ operator fun contains(rule: EmbeddingRule): Boolean {
+ return splitRules.contains(rule)
}
}
@@ -223,4 +302,21 @@
override fun isActivityEmbedded(activity: Activity): Boolean {
return embeddingExtension?.isActivityEmbedded(activity) ?: false
}
+
+ override fun setSplitAttributesCalculator(
+ calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
+ ) {
+ globalLock.withLock {
+ embeddingExtension?.setSplitAttributesCalculator(calculator)
+ }
+ }
+
+ override fun clearSplitAttributesCalculator() {
+ globalLock.withLock {
+ embeddingExtension?.clearSplitAttributesCalculator()
+ }
+ }
+
+ override fun isSplitAttributesCalculatorSupported(): Boolean =
+ embeddingExtension?.isSplitAttributesCalculatorSupported() ?: false
}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/RuleController.kt b/window/window/src/main/java/androidx/window/embedding/RuleController.kt
index 867a1a1..17eac14 100644
--- a/window/window/src/main/java/androidx/window/embedding/RuleController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/RuleController.kt
@@ -38,7 +38,8 @@
private val embeddingBackend: EmbeddingBackend = ExtensionEmbeddingBackend
.getInstance(applicationContext)
- // TODO(b/258356512): Make this a coroutine API that returns Flow<Set<EmbeddingRule>>.
+ // TODO(b/258356512): Make this API a make this a coroutine API that returns
+ // Flow<Set<EmbeddingRule>>.
/**
* Returns a copy of the currently registered rules.
*/
@@ -47,11 +48,17 @@
}
/**
- * Registers a new rule. Will be cleared automatically when the process is stopped.
+ * Registers a new rule, or updates an existing rule if the [tag][EmbeddingRule.tag] has been
+ * registered with [RuleController]. Will be cleared automatically when the process is stopped.
*
- * Note that added rules will **not** be applied to any existing split activity
- * container, and will only be used for new split containers created with future activity
- * launches.
+ * Registering a `SplitRule` may fail if the [SplitController.isSplitSupported]
+ * returns `false`. If not supported, it could be either because
+ * [androidx.window.WindowProperties.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED] not enabled
+ * in AndroidManifest or the feature not available on the device.
+ *
+ * Note that registering a new rule or updating the existing rule will **not** be applied to any
+ * existing split activity container, and will only be used for new split containers created
+ * with future activity launches.
*
* @param rule new [EmbeddingRule] to register.
*/
@@ -81,6 +88,11 @@
* - [SplitPlaceholderRule.Builder]
* - [ActivityRule.Builder]
*
+ * Registering `SplitRule`s may fail if the [SplitController.isSplitSupported]
+ * returns `false`. If not supported, it could be either because
+ * [androidx.window.WindowProperties.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED] not enabled
+ * in AndroidManifest or the feature not available on the device.
+ *
* Note that updating the existing rules will **not** be applied to any existing split activity
* container, and will only be used for new split containers created with future activity
* launches.
diff --git a/window/window/src/main/java/androidx/window/embedding/RuleParser.kt b/window/window/src/main/java/androidx/window/embedding/RuleParser.kt
index 5d3cea3..c3334c0 100644
--- a/window/window/src/main/java/androidx/window/embedding/RuleParser.kt
+++ b/window/window/src/main/java/androidx/window/embedding/RuleParser.kt
@@ -21,14 +21,13 @@
import android.content.Intent
import android.content.res.Resources
import android.content.res.XmlResourceParser
-import android.util.LayoutDirection
import androidx.annotation.XmlRes
-
import androidx.window.R
import androidx.window.embedding.EmbeddingAspectRatio.Companion.buildAspectRatioFromValue
-import androidx.window.embedding.SplitRule.Companion.FINISH_ALWAYS
-import androidx.window.embedding.SplitRule.Companion.FINISH_NEVER
-
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LOCALE
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.ALWAYS
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.NEVER
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.getFinishBehaviorFromValue
import org.xmlpull.v1.XmlPullParser
/**
@@ -67,14 +66,14 @@
"SplitPairRule" -> {
val splitConfig = parseSplitPairRule(context, parser)
lastSplitPairRule = splitConfig
- rules.add(lastSplitPairRule)
+ rules.addRuleWithDuplicatedTagCheck(lastSplitPairRule)
lastSplitPlaceholderRule = null
lastActivityRule = null
}
"SplitPlaceholderRule" -> {
val placeholderConfig = parseSplitPlaceholderRule(context, parser)
lastSplitPlaceholderRule = placeholderConfig
- rules.add(lastSplitPlaceholderRule)
+ rules.addRuleWithDuplicatedTagCheck(lastSplitPlaceholderRule)
lastActivityRule = null
lastSplitPairRule = null
}
@@ -87,11 +86,11 @@
val splitFilter = parseSplitPairFilter(context, parser)
rules.remove(lastSplitPairRule)
lastSplitPairRule += splitFilter
- rules.add(lastSplitPairRule)
+ rules.addRuleWithDuplicatedTagCheck(lastSplitPairRule)
}
"ActivityRule" -> {
val activityConfig = parseActivityRule(context, parser)
- rules.add(activityConfig)
+ rules.addRuleWithDuplicatedTagCheck(activityConfig)
lastSplitPairRule = null
lastSplitPlaceholderRule = null
lastActivityRule = activityConfig
@@ -106,154 +105,191 @@
if (lastActivityRule != null) {
rules.remove(lastActivityRule)
lastActivityRule += activityFilter
- rules.add(lastActivityRule)
+ rules.addRuleWithDuplicatedTagCheck(lastActivityRule)
} else if (lastSplitPlaceholderRule != null) {
rules.remove(lastSplitPlaceholderRule)
lastSplitPlaceholderRule += activityFilter
- rules.add(lastSplitPlaceholderRule)
+ rules.addRuleWithDuplicatedTagCheck(lastSplitPlaceholderRule)
}
}
}
type = parser.next()
}
-
return rules
}
+ private fun HashSet<EmbeddingRule>.addRuleWithDuplicatedTagCheck(rule: EmbeddingRule) {
+ val tag = rule.tag
+ forEach { addedRule ->
+ if (tag != null && tag == addedRule.tag) {
+ throw IllegalArgumentException("Duplicated tag: $tag for $rule. " +
+ "The tag must be unique in XML rule definition.")
+ }
+ }
+ add(rule)
+ }
+
private fun parseSplitPairRule(
context: Context,
parser: XmlResourceParser
- ): SplitPairRule {
- val ratio: Float
- val minWidthDp: Int
- val minSmallestWidthDp: Int
- val maxAspectRatioInPortrait: Float
- val maxAspectRatioInLandscape: Float
- val layoutDir: Int
- val finishPrimaryWithSecondary: Int
- val finishSecondaryWithPrimary: Int
- val clearTop: Boolean
+ ): SplitPairRule =
context.theme.obtainStyledAttributes(
parser,
R.styleable.SplitPairRule,
0,
0
- ).apply {
- ratio = getFloat(R.styleable.SplitPairRule_splitRatio, 0.5f)
- minWidthDp = getInteger(
+ ).let { typedArray ->
+ val tag = typedArray.getString(R.styleable.SplitPairRule_tag)
+ val ratio = typedArray.getFloat(R.styleable.SplitPairRule_splitRatio, 0.5f)
+ val minWidthDp = typedArray.getInteger(
R.styleable.SplitPairRule_splitMinWidthDp,
SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
)
- minSmallestWidthDp = getInteger(
+ val minHeightDp = typedArray.getInteger(
+ R.styleable.SplitPairRule_splitMinHeightDp,
+ SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
+ )
+ val minSmallestWidthDp = typedArray.getInteger(
R.styleable.SplitPairRule_splitMinSmallestWidthDp,
SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
)
- maxAspectRatioInPortrait = getFloat(
+ val maxAspectRatioInPortrait = typedArray.getFloat(
R.styleable.SplitPairRule_splitMaxAspectRatioInPortrait,
SplitRule.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT.value
)
- maxAspectRatioInLandscape = getFloat(
+ val maxAspectRatioInLandscape = typedArray.getFloat(
R.styleable.SplitPairRule_splitMaxAspectRatioInLandscape,
SplitRule.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT.value
)
- layoutDir = getInt(
+ val layoutDir = typedArray.getInt(
R.styleable.SplitPairRule_splitLayoutDirection,
- LayoutDirection.LOCALE
+ LOCALE.value
)
- finishPrimaryWithSecondary =
- getInt(R.styleable.SplitPairRule_finishPrimaryWithSecondary, FINISH_NEVER)
- finishSecondaryWithPrimary =
- getInt(R.styleable.SplitPairRule_finishSecondaryWithPrimary, FINISH_ALWAYS)
- clearTop =
- getBoolean(R.styleable.SplitPairRule_clearTop, false)
+ val finishPrimaryWithSecondary = typedArray.getInt(
+ R.styleable.SplitPairRule_finishPrimaryWithSecondary,
+ NEVER.value
+ )
+ val finishSecondaryWithPrimary = typedArray.getInt(
+ R.styleable.SplitPairRule_finishSecondaryWithPrimary,
+ ALWAYS.value
+ )
+ val clearTop = typedArray.getBoolean(R.styleable.SplitPairRule_clearTop, false)
+ val animationBackgroundColor = typedArray.getColor(
+ R.styleable.SplitPairRule_animationBackgroundColor,
+ 0)
+ typedArray.recycle()
+
+ val defaultAttrs = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.buildSplitTypeFromValue(ratio))
+ .setLayoutDirection(
+ SplitAttributes.LayoutDirection.getLayoutDirectionFromValue(layoutDir)
+ )
+ .setAnimationBackgroundColor(animationBackgroundColor)
+ .build()
+
+ SplitPairRule.Builder(emptySet())
+ .setTag(tag)
+ .setMinWidthDp(minWidthDp)
+ .setMinHeightDp(minHeightDp)
+ .setMinSmallestWidthDp(minSmallestWidthDp)
+ .setMaxAspectRatioInPortrait(buildAspectRatioFromValue(maxAspectRatioInPortrait))
+ .setMaxAspectRatioInLandscape(buildAspectRatioFromValue(maxAspectRatioInLandscape))
+ .setFinishPrimaryWithSecondary(
+ getFinishBehaviorFromValue(finishPrimaryWithSecondary))
+ .setFinishSecondaryWithPrimary(
+ getFinishBehaviorFromValue(finishSecondaryWithPrimary))
+ .setClearTop(clearTop)
+ .setDefaultSplitAttributes(defaultAttrs)
+ .build()
}
- return SplitPairRule.Builder(emptySet())
- .setMinWidthDp(minWidthDp)
- .setMinSmallestWidthDp(minSmallestWidthDp)
- .setMaxAspectRatioInPortrait(buildAspectRatioFromValue(maxAspectRatioInPortrait))
- .setMaxAspectRatioInLandscape(buildAspectRatioFromValue(maxAspectRatioInLandscape))
- .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
- .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
- .setClearTop(clearTop)
- .setSplitRatio(ratio)
- .setLayoutDirection(layoutDir)
- .build()
- }
private fun parseSplitPlaceholderRule(
context: Context,
parser: XmlResourceParser
- ): SplitPlaceholderRule {
- val placeholderActivityIntentName: String?
- val stickyPlaceholder: Boolean
- val finishPrimaryWithPlaceholder: Int
- val ratio: Float
- val minWidthDp: Int
- val minSmallestWidthDp: Int
- val maxAspectRatioInPortrait: Float
- val maxAspectRatioInLandscape: Float
- val layoutDir: Int
+ ): SplitPlaceholderRule =
context.theme.obtainStyledAttributes(
parser,
R.styleable.SplitPlaceholderRule,
0,
0
- ).apply {
- placeholderActivityIntentName = getString(
+ ).let { typedArray ->
+ val tag = typedArray.getString(R.styleable.SplitPlaceholderRule_tag)
+ val placeholderActivityIntentName = typedArray.getString(
R.styleable.SplitPlaceholderRule_placeholderActivityName
)
- stickyPlaceholder = getBoolean(R.styleable.SplitPlaceholderRule_stickyPlaceholder,
- false)
- finishPrimaryWithPlaceholder =
- getInt(R.styleable.SplitPlaceholderRule_finishPrimaryWithPlaceholder, FINISH_ALWAYS)
- ratio = getFloat(R.styleable.SplitPlaceholderRule_splitRatio, 0.5f)
- minWidthDp = getInteger(
+ val stickyPlaceholder = typedArray.getBoolean(
+ R.styleable.SplitPlaceholderRule_stickyPlaceholder,
+ false
+ )
+ val finishPrimaryWithPlaceholder = typedArray.getInt(
+ R.styleable.SplitPlaceholderRule_finishPrimaryWithPlaceholder,
+ ALWAYS.value
+ )
+ if (finishPrimaryWithPlaceholder == NEVER.value) {
+ throw IllegalArgumentException(
+ "Never is not a valid configuration for Placeholder activities. " +
+ "Please use FINISH_ALWAYS or FINISH_ADJACENT instead or refer to the " +
+ "current API")
+ }
+ val ratio = typedArray.getFloat(R.styleable.SplitPlaceholderRule_splitRatio, 0.5f)
+ val minWidthDp = typedArray.getInteger(
R.styleable.SplitPlaceholderRule_splitMinWidthDp,
SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
)
- minSmallestWidthDp = getInteger(
+ val minHeightDp = typedArray.getInteger(
+ R.styleable.SplitPlaceholderRule_splitMinHeightDp,
+ SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
+ )
+ val minSmallestWidthDp = typedArray.getInteger(
R.styleable.SplitPlaceholderRule_splitMinSmallestWidthDp,
SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
)
- maxAspectRatioInPortrait = getFloat(
+ val maxAspectRatioInPortrait = typedArray.getFloat(
R.styleable.SplitPlaceholderRule_splitMaxAspectRatioInPortrait,
SplitRule.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT.value
)
- maxAspectRatioInLandscape = getFloat(
+ val maxAspectRatioInLandscape = typedArray.getFloat(
R.styleable.SplitPlaceholderRule_splitMaxAspectRatioInLandscape,
SplitRule.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT.value
)
- layoutDir = getInt(
+ val layoutDir = typedArray.getInt(
R.styleable.SplitPlaceholderRule_splitLayoutDirection,
- LayoutDirection.LOCALE
+ LOCALE.value
)
- }
- if (finishPrimaryWithPlaceholder == FINISH_NEVER) {
- throw IllegalArgumentException(
- "FINISH_NEVER is not a valid configuration for Placeholder activities. " +
- "Please use FINISH_ALWAYS or FINISH_ADJACENT instead or refer to the " +
- "current API")
- }
- val packageName = context.applicationContext.packageName
- val placeholderActivityClassName = buildClassName(
- packageName,
- placeholderActivityIntentName
- )
+ val animationBackgroundColor = typedArray.getColor(
+ R.styleable.SplitPlaceholderRule_animationBackgroundColor,
+ 0)
+ typedArray.recycle()
- return SplitPlaceholderRule.Builder(
- emptySet(),
- Intent().setComponent(placeholderActivityClassName)
- )
- .setMinWidthDp(minWidthDp)
- .setMinSmallestWidthDp(minSmallestWidthDp)
- .setMaxAspectRatioInPortrait(buildAspectRatioFromValue(maxAspectRatioInPortrait))
- .setMaxAspectRatioInLandscape(buildAspectRatioFromValue(maxAspectRatioInLandscape))
- .setSticky(stickyPlaceholder)
- .setFinishPrimaryWithPlaceholder(finishPrimaryWithPlaceholder)
- .setSplitRatio(ratio)
- .setLayoutDirection(layoutDir)
- .build()
- }
+ val defaultAttrs = SplitAttributes.Builder()
+ .setSplitType(SplitAttributes.SplitType.buildSplitTypeFromValue(ratio))
+ .setLayoutDirection(
+ SplitAttributes.LayoutDirection.getLayoutDirectionFromValue(layoutDir)
+ )
+ .setAnimationBackgroundColor(animationBackgroundColor)
+ .build()
+ val packageName = context.applicationContext.packageName
+ val placeholderActivityClassName = buildClassName(
+ packageName,
+ placeholderActivityIntentName
+ )
+
+ SplitPlaceholderRule.Builder(
+ emptySet(),
+ Intent().setComponent(placeholderActivityClassName)
+ )
+ .setTag(tag)
+ .setMinWidthDp(minWidthDp)
+ .setMinHeightDp(minHeightDp)
+ .setMinSmallestWidthDp(minSmallestWidthDp)
+ .setMaxAspectRatioInPortrait(buildAspectRatioFromValue(maxAspectRatioInPortrait))
+ .setMaxAspectRatioInLandscape(buildAspectRatioFromValue(maxAspectRatioInLandscape))
+ .setSticky(stickyPlaceholder)
+ .setFinishPrimaryWithPlaceholder(
+ getFinishBehaviorFromValue(finishPrimaryWithPlaceholder))
+ .setDefaultSplitAttributes(defaultAttrs)
+ .build()
+ }
private fun parseSplitPairFilter(
context: Context,
@@ -289,18 +325,23 @@
private fun parseActivityRule(
context: Context,
parser: XmlResourceParser
- ): ActivityRule {
- val alwaysExpand: Boolean
+ ): ActivityRule =
context.theme.obtainStyledAttributes(
parser,
R.styleable.ActivityRule,
0,
0
- ).apply {
- alwaysExpand = getBoolean(R.styleable.ActivityRule_alwaysExpand, false)
+ ).let { typedArray ->
+ val tag = typedArray.getString(R.styleable.ActivityRule_tag)
+ val alwaysExpand = typedArray.getBoolean(R.styleable.ActivityRule_alwaysExpand, false)
+ typedArray.recycle()
+
+ val builder = ActivityRule.Builder(emptySet()).setAlwaysExpand(alwaysExpand)
+ if (tag != null) {
+ builder.setTag(tag)
+ }
+ builder.build()
}
- return ActivityRule.Builder(emptySet()).setAlwaysExpand(alwaysExpand).build()
- }
private fun parseActivityFilter(
context: Context,
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt b/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt
new file mode 100644
index 0000000..d83ce52
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt
@@ -0,0 +1,535 @@
+/*
+ * 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.embedding
+
+import android.annotation.SuppressLint
+import androidx.annotation.ColorInt
+import androidx.annotation.FloatRange
+import androidx.annotation.IntRange
+import androidx.window.core.SpecificationComputer.Companion.startSpecification
+import androidx.window.core.VerificationMode
+import androidx.window.embedding.SplitAttributes.LayoutDirection
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LOCALE
+import androidx.window.embedding.SplitAttributes.SplitType
+import androidx.window.embedding.SplitAttributes.SplitType.Companion.splitEqually
+
+/**
+ * Attributes that describe how the parent window (typically the activity task
+ * window) is split between the primary and secondary activity containers,
+ * including:
+ * - Split type — Categorizes the split and specifies the sizes of the
+ * primary and secondary activity containers relative to the parent bounds
+ * - Layout direction — Specifies whether the parent window is split
+ * vertically or horizontally and in which direction the primary and
+ * secondary containers are respectively positioned (left to right, right to
+ * left, top to bottom, and so forth)
+ * - Animation background color — The color of the background during
+ * animation of the split involving this `SplitAttributes` object if the
+ * animation requires a background
+ *
+ * Attributes can be configured by:
+ * - Setting the default `SplitAttributes` using
+ * [SplitPairRule.Builder.setDefaultSplitAttributes] or
+ * [SplitPlaceholderRule.Builder.setDefaultSplitAttributes].
+ * - Setting `splitRatio`, `splitLayoutDirection`, and
+ * `animationBackgroundColor` attributes in `<SplitPairRule>` or
+ * `<SplitPlaceholderRule>` tags in an XML configuration file. The
+ * attributes are parsed as [SplitType], [LayoutDirection], and [ColorInt],
+ * respectively. Note that [SplitType.HingeSplitType] is not supported XML
+ * format.
+ * - Using
+ * [SplitAttributesCalculator.computeSplitAttributesForParams] to customize
+ * the `SplitAttributes` for a given device and window state.
+ *
+ * @see SplitAttributes.SplitType
+ * @see SplitAttributes.LayoutDirection
+ */
+class SplitAttributes internal constructor(
+
+ /**
+ * The split type attribute. Defaults to an equal split of the parent window
+ * for the primary and secondary containers.
+ */
+ val splitType: SplitType = splitEqually(),
+
+ /**
+ * The layout direction attribute for the parent window split. The default
+ * is based on locale.
+ */
+ val layoutDirection: LayoutDirection = LOCALE,
+
+ /**
+ * The [ColorInt] to use for the background color during the animation of
+ * the split involving this `SplitAttributes` object if the animation
+ * requires a background.
+ *
+ * The default is 0, which specifies the theme window background color.
+ */
+ @ColorInt
+ val animationBackgroundColor: Int = 0
+) {
+
+ /**
+ * The type of parent window split, which defines the proportion of the
+ * parent window occupied by the primary and secondary activity containers.
+ */
+ open class SplitType internal constructor(
+
+ /**
+ * The description of this `SplitType`.
+ */
+ internal val description: String,
+
+ /**
+ * An identifier for the split type.
+ *
+ * Used in the evaluation in the `equals()` method.
+ */
+ internal val value: Float,
+
+ ) {
+
+ /**
+ * A string representation of this split type.
+ *
+ * @return The string representation of the object.
+ */
+ override fun toString(): String = description
+
+ /**
+ * Determines whether this object is the same type of split as the
+ * compared object.
+ *
+ * @param other The object to compare to this object.
+ * @return True if the objects are the same split type, false otherwise.
+ */
+ override fun equals(other: Any?): Boolean {
+ if (other === this) return true
+ if (other !is SplitType) return false
+ return value == other.value &&
+ description == other.description
+ }
+
+ /**
+ * Returns a hash code for this split type.
+ *
+ * @return The hash code for this object.
+ */
+ override fun hashCode(): Int = description.hashCode() + 31 * value.hashCode()
+
+ /**
+ * A window split that's based on the ratio of the size of the primary
+ * container to the size of the parent window.
+ *
+ * @see SplitAttributes.SplitType.ratio
+ */
+ class RatioSplitType internal constructor(
+
+ /**
+ * The proportion of the parent window occupied by the primary
+ * container of the split.
+ */
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
+ val ratio: Float
+
+ ) : SplitType("ratio:$ratio", ratio)
+
+ /**
+ * A window split in which the primary and secondary activity containers
+ * each occupy the entire parent window.
+ *
+ * The secondary container overlays the primary container.
+ *
+ * @see SplitAttributes.SplitType.ExpandContainersSplitType
+ */
+ class ExpandContainersSplitType internal constructor() : SplitType("expandContainer", 0.0f)
+
+ /**
+ * A parent window split that conforms to a hinge or separating fold in
+ * the device display.
+ *
+ * @see SplitAttributes.SplitType.splitByHinge
+ */
+ class HingeSplitType internal constructor(
+
+ /**
+ * The split type to use if a split based on the device hinge or
+ * separating fold cannot be determined.
+ */
+ val fallbackSplitType: SplitType
+
+ ) : SplitType("hinge, fallback=$fallbackSplitType", -1.0f)
+
+ /**
+ * Methods that create various split types.
+ */
+ companion object {
+
+ /**
+ * Creates a split type based on the proportion of the parent window
+ * occupied by the primary container of the split.
+ *
+ * Values in the non-inclusive range (0.0, 1.0) define the size of
+ * the primary container relative to the size of the parent window:
+ * - 0.5 — Primary container occupies half of the parent
+ * window; secondary container, the other half
+ * - > 0.5 — Primary container occupies a larger proportion
+ * of the parent window than the secondary container
+ * - < 0.5 — Primary container occupies a smaller
+ * proportion of the parent window than the secondary container
+ *
+ * @param ratio The proportion of the parent window occupied by the
+ * primary container of the split.
+ * @return An instance of [RatioSplitType] with the specified ratio.
+ */
+ @JvmStatic
+ fun ratio(
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
+ ratio: Float
+ ): RatioSplitType {
+ val checkedRatio = ratio.startSpecification(
+ TAG,
+ VerificationMode.STRICT
+ ).require("Ratio must be in range (0.0, 1.0). " +
+ "Use SplitType.expandContainers() instead of 0 or 1.") {
+ ratio in 0.0..1.0 && ratio !in arrayOf(0.0f, 1.0f)
+ }.compute()!!
+ return RatioSplitType(checkedRatio)
+ }
+
+ private val EXPAND_CONTAINERS = ExpandContainersSplitType()
+
+ /**
+ * Creates a split type in which the primary and secondary activity
+ * containers each expand to fill the parent window; the secondary
+ * container overlays the primary container.
+ *
+ * Use this method with the function set in
+ * [SplitController.setSplitAttributesCalculator] to expand the activity containers in
+ * some device states. The following sample shows how to always fill the parent bounds
+ * if the device is in portrait orientation:
+ *
+ * @sample androidx.window.samples.embedding.expandContainersInPortrait
+ *
+ * @return An instance of [ExpandContainersSplitType].
+ */
+ @JvmStatic
+ fun expandContainers(): ExpandContainersSplitType = EXPAND_CONTAINERS
+
+ /**
+ * Creates a split type in which the primary and secondary
+ * containers occupy equal portions of the parent window.
+ *
+ * Serves as the default [SplitType].
+ *
+ * @return A `RatioSplitType` in which the activity containers
+ * occupy equal portions of the parent window.
+ */
+ @JvmStatic
+ fun splitEqually(): RatioSplitType = ratio(0.5f)
+
+ /**
+ * Creates a split type in which the split ratio conforms to the
+ * position of a hinge or separating fold in the device display.
+ *
+ * The split type is created only if:
+ * <ul>
+ * <li>The host task is not in multi-window mode (e.g.,
+ * split-screen mode or picture-in-picture mode)</li>
+ * <li>The device has a hinge or separating fold reported by
+ * [androidx.window.layout.FoldingFeature.isSeparating]</li>
+ * <li>The hinge or separating fold orientation matches how the
+ * parent bounds are split:
+ * <ul style="list-style-type: circle;">
+ * <li>The hinge or fold orientation is vertical, and
+ * the parent bounds are also split vertically
+ * (containers are side by side)</li>
+ * <li>The hinge or fold orientation is horizontal, and
+ * the parent bounds are also split horizontally
+ * (containers are top and bottom)</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * Otherwise, the method falls back to `fallbackSplitType`.
+ *
+ * @param fallbackSplitType The split type to use if a split based
+ * on the device hinge or separating fold cannot be determined.
+ * Can be a [RatioSplitType] or [ExpandContainersSplitType].
+ * Defaults to [SplitType.splitEqually].
+ * @return An instance of [HingeSplitType] with a fallback split
+ * type.
+ */
+ @JvmStatic
+ fun splitByHinge(
+ fallbackSplitType: SplitType = splitEqually()
+ ): HingeSplitType {
+ val checkedType = fallbackSplitType.startSpecification(
+ TAG,
+ VerificationMode.STRICT
+ ).require(
+ "FallbackSplitType must be a RatioSplitType or ExpandContainerSplitType"
+ ) {
+ fallbackSplitType is RatioSplitType ||
+ fallbackSplitType is ExpandContainersSplitType
+ }.compute()!!
+ return HingeSplitType(checkedType)
+ }
+
+ /**
+ * Returns a `SplitType` with the given `value`.
+ */
+ @SuppressLint("Range") // value = 0.0 is covered.
+ @JvmStatic
+ internal fun buildSplitTypeFromValue(
+ @FloatRange(from = 0.0, to = 1.0, toInclusive = false) value: Float
+ ) = if (value == EXPAND_CONTAINERS.value) {
+ expandContainers()
+ } else {
+ ratio(value)
+ }
+ }
+ }
+
+ /**
+ * The layout direction of the primary and secondary activity containers.
+ */
+ class LayoutDirection private constructor(
+
+ /**
+ * The description of this `LayoutDirection`.
+ */
+ private val description: String,
+
+ /**
+ * The enum value defined in `splitLayoutDirection` attributes in
+ * `attrs.xml`.
+ */
+ internal val value: Int,
+
+ ) {
+
+ /**
+ * A string representation of this `LayoutDirection`.
+ *
+ * @return The string representation of the object.
+ */
+ override fun toString(): String = description
+
+ /**
+ * Non-public properties and methods.
+ */
+ companion object {
+ /**
+ * Specifies that the parent bounds are split vertically (side to
+ * side).
+ *
+ * The direction of the primary and secondary containers is deduced
+ * from the locale as either `LEFT_TO_RIGHT` or `RIGHT_TO_LEFT`.
+ *
+ * See also [layoutDirection].
+ */
+ @JvmField
+ val LOCALE = LayoutDirection("LOCALE", 0)
+ /**
+ * Specifies that the parent bounds are split vertically (side to
+ * side).
+ *
+ * Places the primary container in the left portion of the parent
+ * window, and the secondary container in the right portion.
+ *
+ * <img width="70%" height="70%" src="/images/guide/topics/large-screens/activity-embedding/reference-docs/a_to_a_b_ltr.png" alt="Activity A starts activity B to the right."/>
+ *
+ * See also [layoutDirection].
+ */
+ @JvmField
+ val LEFT_TO_RIGHT = LayoutDirection("LEFT_TO_RIGHT", 1)
+ /**
+ * Specifies that the parent bounds are split vertically (side to
+ * side).
+ *
+ * Places the primary container in the right portion of the parent
+ * window, and the secondary container in the left portion.
+ *
+ * <img width="70%" height="70%" src="/images/guide/topics/large-screens/activity-embedding/reference-docs/a_to_a_b_rtl.png" alt="Activity A starts activity B to the left."/>
+ *
+ * See also [layoutDirection].
+ */
+ @JvmField
+ val RIGHT_TO_LEFT = LayoutDirection("RIGHT_TO_LEFT", 2)
+ /**
+ * Specifies that the parent bounds are split horizontally (top and
+ * bottom).
+ *
+ * Places the primary container in the top portion of the parent
+ * window, and the secondary container in the bottom portion.
+ *
+ * <img width="70%" height="70%" src="/images/guide/topics/large-screens/activity-embedding/reference-docs/a_to_a_b_ttb.png" alt="Activity A starts activity B to the bottom."/>
+ *
+ * If the horizontal layout direction is not supported on the
+ * device, layout direction falls back to `LOCALE`.
+ *
+ * See also [layoutDirection].
+ */
+ @JvmField
+ val TOP_TO_BOTTOM = LayoutDirection("TOP_TO_BOTTOM", 3)
+ /**
+ * Specifies that the parent bounds are split horizontally (top and
+ * bottom).
+ *
+ * Places the primary container in the bottom portion of the parent
+ * window, and the secondary container in the top portion.
+ *
+ * <img width="70%" height="70%" src="/images/guide/topics/large-screens/activity-embedding/reference-docs/a_to_a_b_btt.png" alt="Activity A starts activity B to the top."/>
+ *
+ * If the horizontal layout direction is not supported on the
+ * device, layout direction falls back to `LOCALE`.
+ *
+ * See also [layoutDirection].
+ */
+ @JvmField
+ val BOTTOM_TO_TOP = LayoutDirection("BOTTOM_TO_TOP", 4)
+
+ /**
+ * Returns `LayoutDirection` with the given `value`.
+ */
+ @JvmStatic
+ internal fun getLayoutDirectionFromValue(
+ @IntRange(from = 0, to = 4) value: Int
+ ) = when (value) {
+ LEFT_TO_RIGHT.value -> LEFT_TO_RIGHT
+ RIGHT_TO_LEFT.value -> RIGHT_TO_LEFT
+ LOCALE.value -> LOCALE
+ TOP_TO_BOTTOM.value -> TOP_TO_BOTTOM
+ BOTTOM_TO_TOP.value -> BOTTOM_TO_TOP
+ else -> throw IllegalArgumentException("Undefined value:$value")
+ }
+ }
+ }
+
+ /**
+ * Non-public properties and methods.
+ */
+ companion object {
+ private val TAG = SplitAttributes::class.java.simpleName
+ }
+
+ /**
+ * Returns a hash code for this `SplitAttributes` object.
+ *
+ * @return The hash code for this object.
+ */
+ override fun hashCode(): Int {
+ var result = splitType.hashCode()
+ result = result * 31 + layoutDirection.hashCode()
+ result = result * 31 + animationBackgroundColor.hashCode()
+ return result
+ }
+
+ /**
+ * Determines whether this object has the same split attributes as the
+ * compared object.
+ *
+ * @param other The object to compare to this object.
+ * @return True if the objects have the same split attributes, false
+ * otherwise.
+ */
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is SplitAttributes) return false
+ return splitType == other.splitType &&
+ layoutDirection == other.layoutDirection &&
+ animationBackgroundColor == other.animationBackgroundColor
+ }
+
+ /**
+ * A string representation of this `SplitAttributes` object.
+ *
+ * @return The string representation of the object.
+ */
+ override fun toString(): String =
+ "${SplitAttributes::class.java.simpleName}:" +
+ "{splitType=$splitType, layoutDir=$layoutDirection," +
+ " animationBackgroundColor=${Integer.toHexString(animationBackgroundColor)}"
+
+ /**
+ * Builder for creating an instance of [SplitAttributes].
+ *
+ * The default split type is an equal split between primary and secondary
+ * containers. The default layout direction is based on locale. The default
+ * animation background color is 0, which specifies the theme window
+ * background color.
+ */
+ class Builder {
+ private var splitType: SplitType = splitEqually()
+ private var layoutDirection = LOCALE
+ @ColorInt
+ private var animationBackgroundColor = 0
+
+ /**
+ * Sets the split type attribute.
+ *
+ * The default is an equal split between primary and secondary
+ * containers.
+ *
+ * @param type The split type attribute.
+ * @return This `Builder`.
+ *
+ * @see SplitAttributes.SplitType
+ */
+ fun setSplitType(type: SplitType): Builder = apply { splitType = type }
+
+ /**
+ * Sets the split layout direction attribute.
+ *
+ * The default is based on locale.
+ *
+ * @param layoutDirection The layout direction attribute.
+ * @return This `Builder`.
+ *
+ * @see SplitAttributes.LayoutDirection
+ */
+ fun setLayoutDirection(layoutDirection: LayoutDirection): Builder =
+ apply { this.layoutDirection = layoutDirection }
+
+ /**
+ * Sets the [ColorInt] to use for the background color during animation
+ * of the split involving this `SplitAttributes` object if the animation
+ * requires a background.
+ *
+ * The default is 0, which specifies the theme window background color.
+ *
+ * @param color A packed color int of the form `AARRGGBB`, for the
+ * animation background color.
+ * @return This `Builder`.
+ *
+ * @see SplitAttributes.animationBackgroundColor
+ */
+ fun setAnimationBackgroundColor(@ColorInt color: Int): Builder =
+ apply { this.animationBackgroundColor = color }
+
+ /**
+ * Builds a `SplitAttributes` instance with the attributes specified by
+ * [setSplitType], [setLayoutDirection], and
+ * [setAnimationBackgroundColor].
+ *
+ * @return The new `SplitAttributes` instance.
+ */
+ fun build(): SplitAttributes = SplitAttributes(splitType, layoutDirection,
+ animationBackgroundColor)
+ }
+}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitAttributesCalculatorParams.kt b/window/window/src/main/java/androidx/window/embedding/SplitAttributesCalculatorParams.kt
new file mode 100644
index 0000000..c099362
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/embedding/SplitAttributesCalculatorParams.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.embedding
+
+import android.content.res.Configuration
+import androidx.window.layout.WindowLayoutInfo
+import androidx.window.layout.WindowMetrics
+
+/**
+ * The parameter container used to report the current device and window state in
+ * [SplitController.setSplitAttributesCalculator] and references the corresponding [SplitRule] by
+ * [splitRuleTag] if [SplitPairRule.tag] is specified.
+ */
+class SplitAttributesCalculatorParams internal constructor(
+ /** The parent container's [WindowMetrics] */
+ val parentWindowMetrics: WindowMetrics,
+ /** The parent container's [Configuration] */
+ val parentConfiguration: Configuration,
+ /** The parent container's [WindowLayoutInfo] */
+ val parentWindowLayoutInfo: WindowLayoutInfo,
+ /**
+ * The [SplitRule.defaultSplitAttributes]. It could be from [SplitRule] Builder APIs
+ * ([SplitPairRule.Builder.setDefaultSplitAttributes] or
+ * [SplitPlaceholderRule.Builder.setDefaultSplitAttributes]) or from the `splitRatio` and
+ * `splitLayoutDirection` attributes from static rule definitions.
+ */
+ val defaultSplitAttributes: SplitAttributes,
+ /**
+ * Whether the [parentWindowMetrics] satisfies the dimensions and aspect
+ * ratios requirements specified in the [SplitRule], which are:
+ * - [SplitRule.minWidthDp]
+ * - [SplitRule.minHeightDp]
+ * - [SplitRule.minSmallestWidthDp]
+ * - [SplitRule.maxAspectRatioInPortrait]
+ * - [SplitRule.maxAspectRatioInLandscape]
+ */
+ @get: JvmName("areDefaultConstraintsSatisfied")
+ val areDefaultConstraintsSatisfied: Boolean,
+ /**
+ * The [tag of `SplitRule`][SplitRule.tag] to apply this [SplitAttributes], which is `null`
+ * if the tag is not set.
+ *
+ * @see SplitPairRule.Builder.setTag
+ * @see SplitPlaceholderRule.Builder.setTag
+ */
+ val splitRuleTag: String?,
+) {
+ override fun toString(): String =
+ "${SplitAttributesCalculatorParams::class.java.simpleName}:{" +
+ "windowMetrics=$parentWindowMetrics" +
+ ", configuration=$parentConfiguration" +
+ ", windowLayoutInfo=$parentWindowLayoutInfo" +
+ ", defaultSplitAttributes=$defaultSplitAttributes" +
+ ", areDefaultConstraintsSatisfied=$areDefaultConstraintsSatisfied" +
+ ", tag=$splitRuleTag}"
+}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitController.kt b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
index 4825dc0..874db32 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
@@ -18,26 +18,36 @@
import android.app.Activity
import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.util.Log
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
import androidx.core.util.Consumer
+import androidx.window.WindowProperties
+import androidx.window.core.ExperimentalWindowApi
+import androidx.window.embedding.SplitController.Api31Impl.isSplitPropertyEnabled
+import androidx.window.layout.WindowMetrics
import java.util.concurrent.Executor
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
/**
- * A singleton controller class that gets information about the currently active activity
- * splits and provides interaction points to customize the splits and form new
- * splits.
- *
- * A split is a pair of containers that host activities in the same or different
- * processes, combined under the same parent window of the hosting task.
- *
- * A pair of activities can be put into a split by providing a static or runtime
- * split rule and then launching the activities in the same task using
- * [Activity.startActivity()][android.app.Activity.startActivity].
- */
-class SplitController private constructor(applicationContext: Context) {
+* A singleton controller class that gets information about the currently active activity
+* splits and provides interaction points to customize the splits and form new
+* splits.
+*
+* A split is a pair of containers that host activities in the same or different
+* processes, combined under the same parent window of the hosting task.
+*
+* A pair of activities can be put into a split by providing a static or runtime
+* split rule and then launching the activities in the same task using
+* [Activity.startActivity()][android.app.Activity.startActivity].
+*/
+class SplitController private constructor(private val applicationContext: Context) {
private val embeddingBackend: EmbeddingBackend = ExtensionEmbeddingBackend
.getInstance(applicationContext)
+ private var splitPropertyEnabled: Boolean = false
// TODO(b/258356512): Make this method a flow API
/**
@@ -85,15 +95,97 @@
* `isSplitSupported` always returns `true`, and if the split is collapsed,
* activities are launched on top, following the non-activity embedding
* model.
+ *
+ * Also the [androidx.window.WindowProperties.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED]
+ * must be enabled in AndroidManifest within <application> in order to get the correct
+ * state or `false` will be returned by default.
*/
fun isSplitSupported(): Boolean {
- return embeddingBackend.isSplitSupported()
+ if (!splitPropertyEnabled) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ splitPropertyEnabled = isSplitPropertyEnabled(applicationContext)
+ } else {
+ // The PackageManager#getProperty API is not supported before S, assuming
+ // the property is enabled to keep the same behavior on earlier platforms.
+ splitPropertyEnabled = true
+ }
+ }
+ return splitPropertyEnabled && embeddingBackend.isSplitSupported()
}
+ /**
+ * Sets or replaces the previously registered [SplitAttributes] calculator.
+ *
+ * **Note** that it's callers' responsibility to check if this API is supported by calling
+ * [isSplitAttributesCalculatorSupported] before using the this API. It is suggested to always
+ * set meaningful [SplitRule.defaultSplitAttributes] in case this API is not supported on some
+ * devices.
+ *
+ * Also, replacing the calculator will only update existing split pairs after a change
+ * in the window or device state, such as orientation changes or folding state changes.
+ *
+ * The [SplitAttributes] calculator is a function to compute the current [SplitAttributes] for
+ * the given [SplitRule] with the current device and window state. Then The calculator will be
+ * invoked if either:
+ * - An activity is started and matches a registered [SplitRule].
+ * - A parent configuration is updated and there's an existing split pair.
+ *
+ * By default, [SplitRule.defaultSplitAttributes] are applied if the parent container's
+ * [WindowMetrics] satisfies the [SplitRule]'s dimensions requirements, which are
+ * [SplitRule.minWidthDp], [SplitRule.minHeightDp] and [SplitRule.minSmallestWidthDp].
+ * The [SplitRule.defaultSplitAttributes] can be set by
+ * - [SplitRule] Builder APIs, which are
+ * [SplitPairRule.Builder.setDefaultSplitAttributes] and
+ * [SplitPlaceholderRule.Builder.setDefaultSplitAttributes].
+ * - Specifying with `splitRatio` and `splitLayoutDirection` attributes in `<SplitPairRule>` or
+ * `<SplitPlaceHolderRule>` tags in XML files.
+ *
+ * Developers may want to apply different [SplitAttributes] for different device or window
+ * states. For example, on foldable devices, developers may want to split the screen vertically
+ * if the device is in landscape, fill the screen if the device is in portrait and split
+ * the screen horizontally if the device is in
+ * [tabletop posture](https://developer.android.com/guide/topics/ui/foldables#postures).
+ * In this case, the [SplitAttributes] can be customized by the [SplitAttributes] calculator,
+ * which takes effects after calling this API. Developers can also clear the calculator
+ * by [clearSplitAttributesCalculator].
+ * Then, developers could implement the [SplitAttributes] calculator as the sample linked below
+ * shows.
+ *
+ * @sample androidx.window.samples.embedding.splitAttributesCalculatorSample
+ * @param calculator the function to calculate [SplitAttributes] based on the
+ * [SplitAttributesCalculatorParams]. It will replace the previously set if it exists.
+ * @throws UnsupportedOperationException if [isSplitAttributesCalculatorSupported] reports
+ * `false`
+ */
+ @ExperimentalWindowApi
+ fun setSplitAttributesCalculator(
+ calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
+ ) {
+ embeddingBackend.setSplitAttributesCalculator(calculator)
+ }
+
+ /**
+ * Clears the callback previously set by [setSplitAttributesCalculator].
+ * The caller **must** make sure [isSplitAttributesCalculatorSupported] before invoking.
+ *
+ * @throws UnsupportedOperationException if [isSplitAttributesCalculatorSupported] reports
+ * `false`
+ */
+ @ExperimentalWindowApi
+ fun clearSplitAttributesCalculator() {
+ embeddingBackend.clearSplitAttributesCalculator()
+ }
+
+ /** Returns whether [setSplitAttributesCalculator] is supported or not. */
+ @ExperimentalWindowApi
+ fun isSplitAttributesCalculatorSupported(): Boolean =
+ embeddingBackend.isSplitAttributesCalculatorSupported()
+
companion object {
@Volatile
private var globalInstance: SplitController? = null
private val globalLock = ReentrantLock()
+ private const val TAG = "SplitController"
internal const val sDebug = false
@@ -114,4 +206,31 @@
return globalInstance!!
}
}
+
+ @RequiresApi(31)
+ private object Api31Impl {
+ @DoNotInline
+ fun isSplitPropertyEnabled(applicationContext: Context): Boolean {
+ val property = try {
+ applicationContext.packageManager.getProperty(
+ WindowProperties.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED,
+ applicationContext.packageName
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(
+ TAG, WindowProperties.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED +
+ " must be set and enabled in AndroidManifest.xml to use splits APIs."
+ )
+ return false
+ }
+ if (!property.isBoolean) {
+ Log.e(
+ TAG, WindowProperties.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED +
+ " must have a boolean value"
+ )
+ return false
+ }
+ return property.boolean
+ }
+ }
}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitInfo.kt b/window/window/src/main/java/androidx/window/embedding/SplitInfo.kt
index 3cabc9e..3bfea72 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitInfo.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitInfo.kt
@@ -28,10 +28,8 @@
* The [ActivityStack] representing the secondary split container.
*/
val secondaryActivityStack: ActivityStack,
- /**
- * Ratio of the Task width that is given to the primary split container.
- */
- val splitRatio: Float
+ /** The [SplitAttributes] of this split pair. */
+ val splitAttributes: SplitAttributes
) {
operator fun contains(activity: Activity): Boolean {
return primaryActivityStack.contains(activity) ||
@@ -44,7 +42,7 @@
if (primaryActivityStack != other.primaryActivityStack) return false
if (secondaryActivityStack != other.secondaryActivityStack) return false
- if (splitRatio != other.splitRatio) return false
+ if (splitAttributes != other.splitAttributes) return false
return true
}
@@ -52,7 +50,7 @@
override fun hashCode(): Int {
var result = primaryActivityStack.hashCode()
result = 31 * result + secondaryActivityStack.hashCode()
- result = 31 * result + splitRatio.hashCode()
+ result = 31 * result + splitAttributes.hashCode()
return result
}
@@ -61,7 +59,7 @@
append("SplitInfo:{")
append("primaryActivityStack=$primaryActivityStack,")
append("secondaryActivityStack=$secondaryActivityStack,")
- append("splitRatio=$splitRatio}")
+ append("splitAttributes=$splitAttributes}")
}
}
}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
index c4311b3..76a561c 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
@@ -16,20 +16,18 @@
package androidx.window.embedding
-import android.util.LayoutDirection.LOCALE
-import android.util.LayoutDirection.LTR
-import android.util.LayoutDirection.RTL
-import androidx.annotation.FloatRange
import androidx.annotation.IntRange
import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW
import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_DP_DEFAULT
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.ALWAYS
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.NEVER
/**
- * Split configuration rules for activity pairs. Define when activities that were launched on top of
- * each other should be shown side-by-side, and the visual properties of such splits. Can be set
- * either via [RuleController.setRules] or via [RuleController.addRule]. The rules are always
+ * Split configuration rules for activity pairs. Define when activities that were launched on top
+ * should be placed adjacent to the one below, and the visual properties of such splits. Can be set
+ * either by [RuleController.setRules] or [RuleController.addRule]. The rules are always
* applied only to activities that will be started after the rules were set.
*/
class SplitPairRule : SplitRule {
@@ -44,23 +42,21 @@
* Determines what happens with the primary container when all activities are finished in the
* associated secondary container.
*
- * @see SplitRule.FINISH_NEVER
- * @see SplitRule.FINISH_ALWAYS
- * @see SplitRule.FINISH_ADJACENT
+ * @see SplitRule.FinishBehavior.NEVER
+ * @see SplitRule.FinishBehavior.ALWAYS
+ * @see SplitRule.FinishBehavior.ADJACENT
*/
- @SplitFinishBehavior
- val finishPrimaryWithSecondary: Int
+ val finishPrimaryWithSecondary: FinishBehavior
/**
* Determines what happens with the secondary container when all activities are finished in the
* associated primary container.
*
- * @see SplitRule.FINISH_NEVER
- * @see SplitRule.FINISH_ALWAYS
- * @see SplitRule.FINISH_ADJACENT
+ * @see SplitRule.FinishBehavior.NEVER
+ * @see SplitRule.FinishBehavior.ALWAYS
+ * @see SplitRule.FinishBehavior.ADJACENT
*/
- @SplitFinishBehavior
- val finishSecondaryWithPrimary: Int
+ val finishSecondaryWithPrimary: FinishBehavior
/**
* If there is an existing split with the same primary container, indicates whether the
@@ -70,18 +66,19 @@
val clearTop: Boolean
internal constructor(
+ tag: String? = null,
filters: Set<SplitPairFilter>,
- @SplitFinishBehavior finishPrimaryWithSecondary: Int = FINISH_NEVER,
- @SplitFinishBehavior finishSecondaryWithPrimary: Int = FINISH_ALWAYS,
+ finishPrimaryWithSecondary: FinishBehavior = NEVER,
+ finishSecondaryWithPrimary: FinishBehavior = ALWAYS,
clearTop: Boolean = false,
@IntRange(from = 0) minWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+ @IntRange(from = 0) minHeightDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
@IntRange(from = 0) minSmallestWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
maxAspectRatioInPortrait: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT,
maxAspectRatioInLandscape: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT,
- @FloatRange(from = 0.0, to = 1.0) splitRatio: Float = SPLIT_RATIO_DEFAULT,
- @LayoutDirection layoutDirection: Int = LOCALE
- ) : super(minWidthDp, minSmallestWidthDp, maxAspectRatioInPortrait, maxAspectRatioInLandscape,
- splitRatio, layoutDirection) {
+ defaultSplitAttributes: SplitAttributes,
+ ) : super(tag, minWidthDp, minHeightDp, minSmallestWidthDp, maxAspectRatioInPortrait,
+ maxAspectRatioInLandscape, defaultSplitAttributes) {
this.filters = filters.toSet()
this.clearTop = clearTop
this.finishPrimaryWithSecondary = finishPrimaryWithSecondary
@@ -95,23 +92,21 @@
* one of the provided filters matches.
*/
class Builder(
- private val filters: Set<SplitPairFilter>,
+ private val filters: Set<SplitPairFilter>
) {
+ private var tag: String? = null
@IntRange(from = 0)
private var minWidthDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
@IntRange(from = 0)
+ private var minHeightDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
+ @IntRange(from = 0)
private var minSmallestWidthDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
private var maxAspectRatioInPortrait = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
private var maxAspectRatioInLandscape = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
- @SplitFinishBehavior
- private var finishPrimaryWithSecondary = FINISH_NEVER
- @SplitFinishBehavior
- private var finishSecondaryWithPrimary = FINISH_ALWAYS
+ private var finishPrimaryWithSecondary = NEVER
+ private var finishSecondaryWithPrimary = ALWAYS
private var clearTop = false
- @FloatRange(from = 0.0, to = 1.0)
- private var splitRatio = SPLIT_RATIO_DEFAULT
- @LayoutDirection
- private var layoutDirection = LOCALE
+ private var defaultSplitAttributes = SplitAttributes.Builder().build()
/**
* Sets the smallest value of width of the parent window when the split should be used, in
@@ -127,6 +122,24 @@
apply { this.minWidthDp = minWidthDp }
/**
+ * Sets the smallest value of height of the parent task window when the split should be
+ * used, in DP. When the window size is smaller than requested here, activities in the
+ * secondary container will be stacked on top of the activities in the primary one,
+ * completely overlapping them.
+ *
+ * It is useful if it's necessary to split the parent window horizontally for this
+ * [SplitPairRule].
+ *
+ * The default is [SPLIT_MIN_DIMENSION_DP_DEFAULT] if the app doesn't set.
+ * [SPLIT_MIN_DIMENSION_ALWAYS_ALLOW] means to always allow split.
+ *
+ * @see SplitAttributes.LayoutDirection.TOP_TO_BOTTOM
+ * @see SplitAttributes.LayoutDirection.BOTTOM_TO_TOP
+ */
+ fun setMinHeightDp(@IntRange(from = 0) minHeightDp: Int): Builder =
+ apply { this.minHeightDp = minHeightDp }
+
+ /**
* Sets the smallest value of the smallest possible width of the parent window in any
* rotation when the split should be used, in DP. When the window size is smaller than
* requested here, activities in the secondary container will be stacked on top of the
@@ -179,12 +192,12 @@
* Sets the behavior of the primary container when all activities are finished in the
* associated secondary container.
*
- * @see SplitRule.FINISH_NEVER
- * @see SplitRule.FINISH_ALWAYS
- * @see SplitRule.FINISH_ADJACENT
+ * @see SplitRule.FinishBehavior.NEVER
+ * @see SplitRule.FinishBehavior.ALWAYS
+ * @see SplitRule.FinishBehavior.ADJACENT
*/
fun setFinishPrimaryWithSecondary(
- @SplitFinishBehavior finishPrimaryWithSecondary: Int
+ finishPrimaryWithSecondary: FinishBehavior
): Builder =
apply { this.finishPrimaryWithSecondary = finishPrimaryWithSecondary }
@@ -192,12 +205,12 @@
* Sets the behavior of the secondary container when all activities are finished in the
* associated primary container.
*
- * @see SplitRule.FINISH_NEVER
- * @see SplitRule.FINISH_ALWAYS
- * @see SplitRule.FINISH_ADJACENT
+ * @see SplitRule.FinishBehavior.NEVER
+ * @see SplitRule.FinishBehavior.ALWAYS
+ * @see SplitRule.FinishBehavior.ADJACENT
*/
fun setFinishSecondaryWithPrimary(
- @SplitFinishBehavior finishSecondaryWithPrimary: Int
+ finishSecondaryWithPrimary: FinishBehavior
): Builder =
apply { this.finishSecondaryWithPrimary = finishSecondaryWithPrimary }
@@ -211,29 +224,34 @@
apply { this.clearTop = clearTop }
/**
- * Sets what part of the width should be given to the primary activity.
- *
- * The default is `0.5` if the app doesn't set, which is to split with equal width.
+ * Sets the default [SplitAttributes] to apply on the activity containers pair when the host
+ * task bounds satisfy [minWidthDp], [minHeightDp], [minSmallestWidthDp],
+ * [maxAspectRatioInPortrait] and [maxAspectRatioInLandscape] requirements.
*/
- fun setSplitRatio(@FloatRange(from = 0.0, to = 1.0) splitRatio: Float): Builder =
- apply { this.splitRatio = splitRatio }
+ fun setDefaultSplitAttributes(defaultSplitAttributes: SplitAttributes): Builder =
+ apply { this.defaultSplitAttributes = defaultSplitAttributes }
/**
- * Sets the layout direction for the split. The value must be one of [LTR], [RTL] or
- * [LOCALE].
- * - [LTR]: It splits the task bounds vertically, and put the primary container on the left
- * portion, and the secondary container on the right portion.
- * - [RTL]: It splits the task bounds vertically, and put the primary container on the right
- * portion, and the secondary container on the left portion.
- * - [LOCALE]: It splits the task bounds vertically, and the direction is deduced from the
- * default language script of locale. The direction can be either [LTR] or [RTL].
+ * Sets a unique string to identify this [SplitPairRule], which defaults to `null`.
+ * The suggested usage is to set the tag to be able to differentiate between different rules
+ * in the [SplitAttributesCalculatorParams.splitRuleTag].
*/
- fun setLayoutDirection(@LayoutDirection layoutDirection: Int): Builder =
- apply { this.layoutDirection = layoutDirection }
+ fun setTag(tag: String?): Builder =
+ apply { this.tag = tag }
- fun build() = SplitPairRule(filters, finishPrimaryWithSecondary, finishSecondaryWithPrimary,
- clearTop, minWidthDp, minSmallestWidthDp, maxAspectRatioInPortrait,
- maxAspectRatioInLandscape, splitRatio, layoutDirection)
+ fun build() = SplitPairRule(
+ tag,
+ filters,
+ finishPrimaryWithSecondary,
+ finishSecondaryWithPrimary,
+ clearTop,
+ minWidthDp,
+ minHeightDp,
+ minSmallestWidthDp,
+ maxAspectRatioInPortrait,
+ maxAspectRatioInLandscape,
+ defaultSplitAttributes,
+ )
}
/**
@@ -245,15 +263,16 @@
newSet.addAll(filters)
newSet.add(filter)
return Builder(newSet.toSet())
+ .setTag(tag)
.setMinWidthDp(minWidthDp)
+ .setMinHeightDp(minHeightDp)
.setMinSmallestWidthDp(minSmallestWidthDp)
.setMaxAspectRatioInPortrait(maxAspectRatioInPortrait)
.setMaxAspectRatioInLandscape(maxAspectRatioInLandscape)
.setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
.setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
.setClearTop(clearTop)
- .setSplitRatio(splitRatio)
- .setLayoutDirection(layoutDirection)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
.build()
}
@@ -281,9 +300,10 @@
override fun toString(): String =
"${SplitPairRule::class.java.simpleName}{" +
- " splitRatio=$splitRatio" +
- ", layoutDirection=$layoutDirection" +
+ "tag=$tag" +
+ ", defaultSplitAttributes=$defaultSplitAttributes" +
", minWidthDp=$minWidthDp" +
+ ", minHeightDp=$minHeightDp" +
", minSmallestWidthDp=$minSmallestWidthDp" +
", maxAspectRatioInPortrait=$maxAspectRatioInPortrait" +
", maxAspectRatioInLandscape=$maxAspectRatioInLandscape" +
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
index d693d22..83ed1ca 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
@@ -17,17 +17,15 @@
package androidx.window.embedding
import android.content.Intent
-import android.util.LayoutDirection.LOCALE
-import android.util.LayoutDirection.LTR
-import android.util.LayoutDirection.RTL
-import androidx.annotation.FloatRange
-import androidx.annotation.IntDef
import androidx.annotation.IntRange
import androidx.core.util.Preconditions.checkArgument
import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW
import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_DP_DEFAULT
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.ALWAYS
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.NEVER
+import androidx.window.extensions.WindowExtensions
/**
* Configuration rules for split placeholders.
@@ -65,39 +63,30 @@
val isSticky: Boolean
/**
- * Defines whether a container should be finished together when the associated placeholder
- * activity is being finished based on current presentation mode.
- */
- @Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
- @Retention(AnnotationRetention.SOURCE)
- @IntDef(FINISH_ALWAYS, FINISH_ADJACENT)
- internal annotation class SplitPlaceholderFinishBehavior
-
- /**
* Determines what happens with the primary container when all activities are finished in the
* associated placeholder container.
*
- * @see SplitRule.FINISH_ALWAYS
- * @see SplitRule.FINISH_ADJACENT
+ * @see SplitRule.FinishBehavior.ALWAYS
+ * @see SplitRule.FinishBehavior.ADJACENT
*/
- @SplitPlaceholderFinishBehavior
- val finishPrimaryWithPlaceholder: Int
+ val finishPrimaryWithPlaceholder: FinishBehavior
internal constructor(
+ tag: String? = null,
filters: Set<ActivityFilter>,
placeholderIntent: Intent,
isSticky: Boolean,
- @SplitPlaceholderFinishBehavior finishPrimaryWithPlaceholder: Int = FINISH_ALWAYS,
+ finishPrimaryWithPlaceholder: FinishBehavior = ALWAYS,
@IntRange(from = 0) minWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+ @IntRange(from = 0) minHeightDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
@IntRange(from = 0) minSmallestWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
maxAspectRatioInPortrait: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT,
maxAspectRatioInLandscape: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT,
- @FloatRange(from = 0.0, to = 1.0) splitRatio: Float = SPLIT_RATIO_DEFAULT,
- @LayoutDirection layoutDirection: Int = LOCALE
- ) : super(minWidthDp, minSmallestWidthDp, maxAspectRatioInPortrait, maxAspectRatioInLandscape,
- splitRatio, layoutDirection) {
- checkArgument(finishPrimaryWithPlaceholder != FINISH_NEVER,
- "FINISH_NEVER is not a valid configuration for SplitPlaceholderRule. " +
+ defaultSplitAttributes: SplitAttributes,
+ ) : super(tag, minWidthDp, minHeightDp, minSmallestWidthDp, maxAspectRatioInPortrait,
+ maxAspectRatioInLandscape, defaultSplitAttributes) {
+ checkArgument(finishPrimaryWithPlaceholder != NEVER,
+ "NEVER is not a valid configuration for SplitPlaceholderRule. " +
"Please use FINISH_ALWAYS or FINISH_ADJACENT instead or refer to the current API.")
this.filters = filters.toSet()
this.placeholderIntent = placeholderIntent
@@ -114,21 +103,20 @@
*/
class Builder(
private val filters: Set<ActivityFilter>,
- private val placeholderIntent: Intent,
+ private val placeholderIntent: Intent
) {
+ private var tag: String? = null
@IntRange(from = 0)
private var minWidthDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
@IntRange(from = 0)
+ private var minHeightDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
+ @IntRange(from = 0)
private var minSmallestWidthDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
private var maxAspectRatioInPortrait = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
private var maxAspectRatioInLandscape = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
- @SplitPlaceholderFinishBehavior
- private var finishPrimaryWithPlaceholder = FINISH_ALWAYS
+ private var finishPrimaryWithPlaceholder = ALWAYS
private var isSticky = false
- @FloatRange(from = 0.0, to = 1.0)
- private var splitRatio = SPLIT_RATIO_DEFAULT
- @LayoutDirection
- private var layoutDirection = LOCALE
+ private var defaultSplitAttributes = SplitAttributes.Builder().build()
/**
* Sets the smallest value of width of the parent window when the split should be used, in
@@ -144,6 +132,24 @@
apply { this.minWidthDp = minWidthDp }
/**
+ * Sets the smallest value of height of the parent task window when the split should be
+ * used, in DP. When the window size is smaller than requested here, activities in the
+ * secondary container will be stacked on top of the activities in the primary one,
+ * completely overlapping them.
+ *
+ * It is useful if it's necessary to split the parent window horizontally for this
+ * [SplitPlaceholderRule].
+ *
+ * The default is [SPLIT_MIN_DIMENSION_DP_DEFAULT] if the app doesn't set.
+ * [SPLIT_MIN_DIMENSION_ALWAYS_ALLOW] means to always allow split.
+ *
+ * @see SplitAttributes.LayoutDirection.TOP_TO_BOTTOM
+ * @see SplitAttributes.LayoutDirection.BOTTOM_TO_TOP
+ */
+ fun setMinHeightDp(@IntRange(from = 0) minHeightDp: Int): Builder =
+ apply { this.minHeightDp = minHeightDp }
+
+ /**
* Sets the smallest value of the smallest possible width of the parent window in any
* rotation when the split should be used, in DP. When the window size is smaller than
* requested here, activities in the secondary container will be stacked on top of the
@@ -196,17 +202,21 @@
* Sets the behavior of the primary container when all activities are finished in the
* associated placeholder container.
*
- * @see SplitRule.FINISH_ALWAYS
- * @see SplitRule.FINISH_ADJACENT
+ * @see SplitRule.FinishBehavior.ALWAYS
+ * @see SplitRule.FinishBehavior.ADJACENT
*/
- fun setFinishPrimaryWithPlaceholder(
- @SplitPlaceholderFinishBehavior finishPrimaryWithPlaceholder: Int
- ): Builder =
+ fun setFinishPrimaryWithPlaceholder(finishPrimaryWithPlaceholder: FinishBehavior): Builder =
apply {
this.finishPrimaryWithPlaceholder = finishPrimaryWithPlaceholder
}
/**
+ * @see SplitPlaceholderRule#getTag
+ * since [WindowExtensions.VENDOR_API_LEVEL_2]
+ */
+ fun setTag(tag: String?): Builder = apply { this.tag = tag }
+
+ /**
* Sets whether the placeholder will show on top in a smaller window size after it first
* appeared in a split with sufficient minimum width.
*/
@@ -214,29 +224,26 @@
apply { this.isSticky = isSticky }
/**
- * Sets what part of the width should be given to the primary activity.
- *
- * The default is `0.5` if the app doesn't set, which is to split with equal width.
+ * Sets the default [SplitAttributes] to apply on the activity containers pair when the host
+ * task bounds satisfy [minWidthDp], [minHeightDp], [minSmallestWidthDp],
+ * [maxAspectRatioInPortrait] and [maxAspectRatioInLandscape] requirements.
*/
- fun setSplitRatio(@FloatRange(from = 0.0, to = 1.0) splitRatio: Float): Builder =
- apply { this.splitRatio = splitRatio }
+ fun setDefaultSplitAttributes(defaultSplitAttributes: SplitAttributes): Builder =
+ apply { this.defaultSplitAttributes = defaultSplitAttributes }
- /**
- * Sets the layout direction for the split. The value must be one of [LTR], [RTL] or
- * [LOCALE].
- * - [LTR]: It splits the task bounds vertically, and put the primary container on the left
- * portion, and the secondary container on the right portion.
- * - [RTL]: It splits the task bounds vertically, and put the primary container on the right
- * portion, and the secondary container on the left portion.
- * - [LOCALE]: It splits the task bounds vertically, and the direction is deduced from the
- * default language script of locale. The direction can be either [LTR] or [RTL].
- */
- fun setLayoutDirection(@LayoutDirection layoutDirection: Int): Builder =
- apply { this.layoutDirection = layoutDirection }
-
- fun build() = SplitPlaceholderRule(filters, placeholderIntent, isSticky,
- finishPrimaryWithPlaceholder, minWidthDp, minSmallestWidthDp, maxAspectRatioInPortrait,
- maxAspectRatioInLandscape, splitRatio, layoutDirection)
+ fun build() = SplitPlaceholderRule(
+ tag,
+ filters,
+ placeholderIntent,
+ isSticky,
+ finishPrimaryWithPlaceholder,
+ minWidthDp,
+ minHeightDp,
+ minSmallestWidthDp,
+ maxAspectRatioInPortrait,
+ maxAspectRatioInLandscape,
+ defaultSplitAttributes,
+ )
}
/**
@@ -248,14 +255,15 @@
newSet.addAll(filters)
newSet.add(filter)
return Builder(newSet.toSet(), placeholderIntent)
+ .setTag(tag)
.setMinWidthDp(minWidthDp)
+ .setMinHeightDp(minHeightDp)
.setMinSmallestWidthDp(minSmallestWidthDp)
.setMaxAspectRatioInPortrait(maxAspectRatioInPortrait)
.setMaxAspectRatioInLandscape(maxAspectRatioInLandscape)
.setSticky(isSticky)
.setFinishPrimaryWithPlaceholder(finishPrimaryWithPlaceholder)
- .setSplitRatio(splitRatio)
- .setLayoutDirection(layoutDirection)
+ .setDefaultSplitAttributes(defaultSplitAttributes)
.build()
}
@@ -282,16 +290,17 @@
}
override fun toString(): String =
- "SplitPlaceholderRule{" +
- " splitRatio=$splitRatio" +
- ", layoutDirection=$layoutDirection" +
- ", minWidthDp=$minWidthDp" +
- ", minSmallestWidthDp=$minSmallestWidthDp" +
- ", maxAspectRatioInPortrait=$maxAspectRatioInPortrait" +
- ", maxAspectRatioInLandscape=$maxAspectRatioInLandscape" +
- ", placeholderIntent=$placeholderIntent" +
- ", isSticky=$isSticky" +
- ", finishPrimaryWithPlaceholder=$finishPrimaryWithPlaceholder" +
- ", filters=$filters" +
- "}"
+ "SplitPlaceholderRule{" +
+ "tag=$tag" +
+ ", defaultSplitAttributes=$defaultSplitAttributes" +
+ ", minWidthDp=$minWidthDp" +
+ ", minHeightDp=$minHeightDp" +
+ ", minSmallestWidthDp=$minSmallestWidthDp" +
+ ", maxAspectRatioInPortrait=$maxAspectRatioInPortrait" +
+ ", maxAspectRatioInLandscape=$maxAspectRatioInLandscape" +
+ ", placeholderIntent=$placeholderIntent" +
+ ", isSticky=$isSticky" +
+ ", finishPrimaryWithPlaceholder=$finishPrimaryWithPlaceholder" +
+ ", filters=$filters" +
+ "}"
}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
index d50a8aa..df33033 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
@@ -19,19 +19,19 @@
import android.content.Context
import android.graphics.Rect
import android.os.Build
-import android.util.LayoutDirection.LOCALE
-import android.util.LayoutDirection.LTR
-import android.util.LayoutDirection.RTL
import android.view.WindowMetrics
import androidx.annotation.DoNotInline
-import androidx.annotation.FloatRange
-import androidx.annotation.IntDef
import androidx.annotation.IntRange
+import androidx.annotation.OptIn
import androidx.annotation.RequiresApi
+import androidx.core.os.BuildCompat
import androidx.core.util.Preconditions
import androidx.window.embedding.EmbeddingAspectRatio.Companion.ALWAYS_ALLOW
import androidx.window.embedding.EmbeddingAspectRatio.Companion.ratio
+import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
+import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_DP_DEFAULT
+import androidx.window.embedding.SplitRule.FinishBehavior.Companion.ADJACENT
import kotlin.math.min
/**
@@ -40,29 +40,58 @@
* via [RuleController.addRule]. The rules are always applied only to activities that will be
* started after the rules were set.
*
+ * Note that regardless of whether the minimal requirements ([minWidthDp], [minHeightDp] and
+ * [minSmallestWidthDp]) are met or not, the callback set in
+ * [SplitController.setSplitAttributesCalculator] will still be called for the rule if the
+ * calculator is registered via [SplitController.setSplitAttributesCalculator].
+ * Whether this [SplitRule]'s minimum requirements are satisfied is dispatched in
+ * [SplitAttributesCalculatorParams.areDefaultConstraintsSatisfied] instead.
+ * The width and height could be verified in the [SplitAttributes] calculator callback
+ * as the sample linked below shows.
+ *
+ * It is useful if this [SplitRule] is supported to split the parent container in different
+ * directions with different device states.
+ *
+ * @sample androidx.window.samples.embedding.splitWithOrientations
* @see androidx.window.embedding.SplitPairRule
* @see androidx.window.embedding.SplitPlaceholderRule
*/
open class SplitRule internal constructor(
+ tag: String? = null,
/**
- * The smallest value of width of the parent window when the split should be used, in DP.
+ * The smallest value of width of the parent task window when the split should be used, in DP.
* When the window size is smaller than requested here, activities in the secondary container
* will be stacked on top of the activities in the primary one, completely overlapping them.
*
- * The default is [SPLIT_MIN_DIMENSION_DP_DEFAULT] if the app doesn't set.
- * [SPLIT_MIN_DIMENSION_ALWAYS_ALLOW] means to always allow split.
+ * Uses `0` to always allow split regardless of the parent task width.
+ * The default is [SPLIT_MIN_DIMENSION_DP_DEFAULT].
*/
@IntRange(from = 0)
val minWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
/**
- * The smallest value of the smallest possible width of the parent window in any rotation
- * when the split should be used, in DP. When the window size is smaller than requested
- * here, activities in the secondary container will be stacked on top of the activities in
- * the primary one, completely overlapping them.
+ * The smallest value of height of the parent task window when the split should be used, in DP.
+ * When the window size is smaller than requested here, activities in the secondary container
+ * will be stacked on top of the activities in the primary one, completely overlapping them.
+ * It is useful if it's necessary to split the parent window horizontally for this [SplitRule].
*
- * The default is [SPLIT_MIN_DIMENSION_DP_DEFAULT] if the app doesn't set.
- * [SPLIT_MIN_DIMENSION_ALWAYS_ALLOW] means to always allow split.
+ * Uses `0` to always allow split regardless of the parent task height.
+ * The default is [SPLIT_MIN_DIMENSION_DP_DEFAULT].
+ *
+ * @see SplitAttributes.LayoutDirection.TOP_TO_BOTTOM
+ * @see SplitAttributes.LayoutDirection.BOTTOM_TO_TOP
+ */
+ @IntRange(from = 0)
+ val minHeightDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+
+ /**
+ * The smallest value of the smallest possible width of the parent task window in any rotation
+ * when the split should be used, in DP. When the window size is smaller than requested here,
+ * activities in the secondary container will be stacked on top of the activities in the primary
+ * one, completely overlapping them.
+ *
+ * Uses `0` to always allow split regardless of the parent task smallest width.
+ * The default is [SPLIT_MIN_DIMENSION_DP_DEFAULT].
*/
@IntRange(from = 0)
val minSmallestWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
@@ -75,8 +104,8 @@
*
* This value is only used when the parent window is in portrait (height >= width).
*
- * The default is [SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT] if the app doesn't set, which is the
- * recommend value to only allow split when the parent window is not too stretched in portrait.
+ * The default is [SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT], which is the recommend value to
+ * only allow split when the parent window is not too stretched in portrait.
*
* @see EmbeddingAspectRatio.ratio
* @see EmbeddingAspectRatio.ALWAYS_ALLOW
@@ -92,8 +121,8 @@
*
* This value is only used when the parent window is in landscape (width > height).
*
- * The default is [SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT] if the app doesn't set, which is
- * the recommend value to always allow split when the parent window is in landscape.
+ * The default is [SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT], which is the recommend value to
+ * always allow split when the parent window is in landscape.
*
* @see EmbeddingAspectRatio.ratio
* @see EmbeddingAspectRatio.ALWAYS_ALLOW
@@ -102,78 +131,23 @@
val maxAspectRatioInLandscape: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT,
/**
- * Defines what part of the width should be given to the primary activity.
- *
- * The default is `0.5` if the app doesn't set, which is to split with equal width.
+ * The default [SplitAttributes] to apply on the activity containers pair when the host task
+ * bounds satisfy [minWidthDp], [minHeightDp] and [minSmallestWidthDp] requirements.
*/
- @FloatRange(from = 0.0, to = 1.0)
- val splitRatio: Float = SPLIT_RATIO_DEFAULT,
-
- /**
- * The layout direction for the split. The value must be one of [LTR], [RTL] or [LOCALE].
- * - [LTR]: It splits the task bounds vertically, and put the primary container on the left
- * portion, and the secondary container on the right portion.
- * - [RTL]: It splits the task bounds vertically, and put the primary container on the right
- * portion, and the secondary container on the left portion.
- * - [LOCALE]: It splits the task bounds vertically, and the direction is deduced from the
- * default language script of locale. The direction can be either [LTR] or [RTL].
- */
- @LayoutDirection
- val layoutDirection: Int = LOCALE
-) : EmbeddingRule() {
+ val defaultSplitAttributes: SplitAttributes,
+) : EmbeddingRule(tag) {
init {
Preconditions.checkArgumentNonnegative(minWidthDp, "minWidthDp must be non-negative")
+ Preconditions.checkArgumentNonnegative(minHeightDp, "minHeightDp must be non-negative")
Preconditions.checkArgumentNonnegative(
minSmallestWidthDp,
"minSmallestWidthDp must be non-negative"
)
- Preconditions.checkArgument(splitRatio in 0.0..1.0, "splitRatio must be in 0.0..1.0 range")
}
- @IntDef(LTR, RTL, LOCALE)
- @Retention(AnnotationRetention.SOURCE)
- internal annotation class LayoutDirection
-
- /**
- * Determines what happens with the associated container when all activities are finished in
- * one of the containers in a split.
- *
- * For example, given that [SplitPairRule.finishPrimaryWithSecondary] is [FINISH_ADJACENT] and
- * secondary container finishes. The primary associated container is finished if it's
- * side-by-side with secondary container. The primary associated container is not finished
- * if it occupies entire task bounds.
- *
- * @see SplitPairRule.finishPrimaryWithSecondary
- * @see SplitPairRule.finishSecondaryWithPrimary
- * @see SplitPlaceholderRule.finishPrimaryWithPlaceholder
- */
companion object {
/**
- * Never finish the associated container.
- * @see SplitRule.Companion
- */
- const val FINISH_NEVER = 0
- /**
- * Always finish the associated container independent of the current presentation mode.
- * @see SplitRule.Companion
- */
- const val FINISH_ALWAYS = 1
- /**
- * Only finish the associated container when displayed side-by-side/adjacent to the one
- * being finished. Does not finish the associated one when containers are stacked on top of
- * each other.
- * @see SplitRule.Companion
- */
- const val FINISH_ADJACENT = 2
-
- /**
- * The default split ratio if it is not set by apps.
- * @see SplitRule.splitRatio
- */
- internal const val SPLIT_RATIO_DEFAULT = 0.5f
-
- /**
* When the min dimension is set to this value, it means to always allow split.
* @see SplitRule.minWidthDp
* @see SplitRule.minSmallestWidthDp
@@ -183,8 +157,6 @@
/**
* The default min dimension in DP for allowing split if it is not set by apps. The value
* reflects [androidx.window.core.layout.WindowWidthSizeClass.MEDIUM].
- * @see SplitRule.minWidthDp
- * @see SplitRule.minSmallestWidthDp
*/
const val SPLIT_MIN_DIMENSION_DP_DEFAULT = 600
@@ -204,24 +176,68 @@
}
/**
- * Defines whether an associated container should be finished together with the one that's
- * already being finished based on their current presentation mode.
+ * Determines what happens with the associated container when all activities are finished in
+ * one of the containers in a split.
+ *
+ * For example, given that [SplitPairRule.finishPrimaryWithSecondary] is [ADJACENT] and
+ * secondary container finishes. The primary associated container is finished if it's
+ * adjacent to the secondary container. The primary associated container is not finished
+ * if it occupies entire task bounds.
+ *
+ * @see SplitPairRule.finishPrimaryWithSecondary
+ * @see SplitPairRule.finishSecondaryWithPrimary
+ * @see SplitPlaceholderRule.finishPrimaryWithPlaceholder
*/
- @Retention(AnnotationRetention.SOURCE)
- @IntDef(FINISH_NEVER, FINISH_ALWAYS, FINISH_ADJACENT)
- internal annotation class SplitFinishBehavior
+ class FinishBehavior private constructor(
+ /** The description of this [FinishBehavior] */
+ private val description: String,
+ /** The enum value defined in `splitLayoutDirection` attributes in `attrs.xml` */
+ internal val value: Int,
+ ) {
+ override fun toString(): String = description
+
+ companion object {
+ /** Never finish the associated container. */
+ @JvmField
+ val NEVER = FinishBehavior("NEVER", 0)
+ /**
+ * Always finish the associated container independent of the current presentation mode.
+ */
+ @JvmField
+ val ALWAYS = FinishBehavior("ALWAYS", 1)
+ /**
+ * Only finish the associated container when displayed adjacent to the one being
+ * finished. Does not finish the associated one when containers are stacked on top of
+ * each other.
+ */
+ @JvmField
+ val ADJACENT = FinishBehavior("ADJACENT", 2)
+
+ @JvmStatic
+ internal fun getFinishBehaviorFromValue(
+ @IntRange(from = 0, to = 2) value: Int
+ ): FinishBehavior =
+ when (value) {
+ NEVER.value -> NEVER
+ ALWAYS.value -> ALWAYS
+ ADJACENT.value -> ADJACENT
+ else -> throw IllegalArgumentException("Unknown finish behavior:$value")
+ }
+ }
+ }
/**
* Verifies if the provided parent bounds satisfy the dimensions and aspect ratio requirements
* to apply the rule.
*/
+ // TODO(b/265089843) remove after Build.VERSION_CODES.U released.
+ @OptIn(markerClass = [BuildCompat.PrereleaseSdkCheck::class])
internal fun checkParentMetrics(context: Context, parentMetrics: WindowMetrics): Boolean {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
return false
}
val bounds = Api30Impl.getBounds(parentMetrics)
- // TODO(b/257000820): Application displayMetrics should only be used as a fallback. Replace
- // with Task density after we include it in WindowMetrics.
+ // TODO(b/265089843) replace with Build.VERSION.SDK_INT >= Build.VERSION_CODES.U
val density = context.resources.displayMetrics.density
return checkParentBounds(density, bounds)
}
@@ -236,9 +252,12 @@
return false
}
val minWidthPx = convertDpToPx(density, minWidthDp)
+ val minHeightPx = convertDpToPx(density, minHeightDp)
val minSmallestWidthPx = convertDpToPx(density, minSmallestWidthDp)
// Always allow split if the min dimensions are 0.
val validMinWidth = minWidthDp == SPLIT_MIN_DIMENSION_ALWAYS_ALLOW || width >= minWidthPx
+ val validMinHeight = minHeightDp == SPLIT_MIN_DIMENSION_ALWAYS_ALLOW ||
+ height >= minHeightPx
val validSmallestMinWidth = minSmallestWidthDp == SPLIT_MIN_DIMENSION_ALWAYS_ALLOW ||
min(width, height) >= minSmallestWidthPx
val validAspectRatio = if (height >= width) {
@@ -250,7 +269,7 @@
maxAspectRatioInLandscape == ALWAYS_ALLOW ||
width * 1f / height <= maxAspectRatioInLandscape.value
}
- return validMinWidth && validSmallestMinWidth && validAspectRatio
+ return validMinWidth && validMinHeight && validSmallestMinWidth && validAspectRatio
}
/**
@@ -272,31 +291,33 @@
if (this === other) return true
if (other !is SplitRule) return false
+ if (!super.equals(other)) return false
if (minWidthDp != other.minWidthDp) return false
+ if (minHeightDp != other.minHeightDp) return false
if (minSmallestWidthDp != other.minSmallestWidthDp) return false
if (maxAspectRatioInPortrait != other.maxAspectRatioInPortrait) return false
if (maxAspectRatioInLandscape != other.maxAspectRatioInLandscape) return false
- if (splitRatio != other.splitRatio) return false
- if (layoutDirection != other.layoutDirection) return false
-
+ if (defaultSplitAttributes != other.defaultSplitAttributes) return false
return true
}
override fun hashCode(): Int {
- var result = minWidthDp
+ var result = super.hashCode()
+ result = 31 * result + minWidthDp
+ result = 31 * result + minHeightDp
result = 31 * result + minSmallestWidthDp
result = 31 * result + maxAspectRatioInPortrait.hashCode()
result = 31 * result + maxAspectRatioInLandscape.hashCode()
- result = 31 * result + splitRatio.hashCode()
- result = 31 * result + layoutDirection
+ result = 31 * result + defaultSplitAttributes.hashCode()
return result
}
override fun toString(): String =
"${SplitRule::class.java.simpleName}{" +
- " splitRatio=$splitRatio" +
- ", layoutDirection=$layoutDirection" +
+ " tag=$tag" +
+ ", defaultSplitAttributes=$defaultSplitAttributes" +
", minWidthDp=$minWidthDp" +
+ ", minHeightDp=$minHeightDp" +
", minSmallestWidthDp=$minSmallestWidthDp" +
", maxAspectRatioInPortrait=$maxAspectRatioInPortrait" +
", maxAspectRatioInLandscape=$maxAspectRatioInLandscape" +
diff --git a/window/window/src/main/java/androidx/window/layout/ContextCompatHelper.kt b/window/window/src/main/java/androidx/window/layout/ContextCompatHelper.kt
new file mode 100644
index 0000000..2bc997d
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/layout/ContextCompatHelper.kt
@@ -0,0 +1,73 @@
+/*
+ * 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/WindowInfoTracker.kt b/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
index 3ae9ec8..242217a 100644
--- a/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
+++ b/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
@@ -18,9 +18,11 @@
import android.app.Activity
import android.content.Context
+import android.inputmethodservice.InputMethodService
import android.util.Log
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
+import androidx.annotation.UiContext
import androidx.window.core.ConsumerAdapter
import androidx.window.layout.adapter.WindowBackend
import androidx.window.layout.adapter.extensions.ExtensionWindowLayoutInfoBackend
@@ -37,10 +39,48 @@
* A [Flow] of [WindowLayoutInfo] that contains all the available features. A [WindowLayoutInfo]
* contains a [List] of [DisplayFeature] that intersect the associated [android.view.Window].
*
+ * This method exports the same content as
+ * [WindowLayoutInfo.windowLayoutInfo(activity: Activity)], but also supports non-Activity
+ * windows to receive [WindowLayoutInfo] updates. A [WindowLayoutInfo] value should be published
+ * when [DisplayFeature] have changed, but the behavior is ultimately decided by the hardware
+ * implementation. It is recommended to test the following scenarios:
+ *
+ * * Values are emitted immediately after subscribing to this function.
+ * * There is a long delay between subscribing and receiving the first value.
+ * * Never receiving a value after subscription.
+ *
+ * A derived class may throw NotImplementedError if this method is not overridden.
+ * Obtaining a [WindowInfoTracker] through [WindowInfoTracker.getOrCreate] guarantees having a
+ * default implementation for this method.
+ *
+ * @param context a [UiContext] such as an [Activity], an [InputMethodService], or an instance
+ * created via [Context.createWindowContext] that listens to configuration changes.
+ * @see WindowLayoutInfo
+ * @see DisplayFeature
+ *
+ * @throws NotImplementedError when [Context] is not an [UiContext] or this method has no
+ * supporting implementation.
+ */
+ fun windowLayoutInfo(@UiContext context: Context): Flow<WindowLayoutInfo> {
+ val windowLayoutInfoFlow: Flow<WindowLayoutInfo>? = windowLayoutInfo((context as Activity))
+ return windowLayoutInfoFlow
+ ?: throw NotImplementedError(
+ message = "Must override windowLayoutInfo(context) and provide an implementation.")
+ }
+
+ /**
+ * A [Flow] of [WindowLayoutInfo] that contains all the available features. A [WindowLayoutInfo]
+ * contains a [List] of [DisplayFeature] that intersect the associated [android.view.Window].
+ *
* The first [WindowLayoutInfo] will not be emitted until [Activity.onStart] has been called.
- * which values you receive and when is device dependent. It is recommended to test scenarios
- * where there is a long delay between subscribing and receiving the first value or never
- * receiving a value, since it may differ between hardware implementations.
+ * which values you receive and when is device dependent.
+ *
+ * It is recommended to test the following scenarios since behaviors may differ between hardware
+ * implementations:
+ *
+ * * Values are emitted immediately after subscribing to this function.
+ * * There is a long delay between subscribing and receiving the first value.
+ * * Never receiving a value after subscription.
*
* Since the information is associated to the [Activity] you should not retain the [Flow] across
* [Activity] recreations. Doing so can result in unpredictable behavior such as a memory leak
diff --git a/window/window/src/main/java/androidx/window/layout/WindowInfoTrackerImpl.kt b/window/window/src/main/java/androidx/window/layout/WindowInfoTrackerImpl.kt
index 16efaad..0796437 100644
--- a/window/window/src/main/java/androidx/window/layout/WindowInfoTrackerImpl.kt
+++ b/window/window/src/main/java/androidx/window/layout/WindowInfoTrackerImpl.kt
@@ -18,6 +18,7 @@
import android.app.Activity
import android.content.Context
+import androidx.annotation.UiContext
import androidx.core.util.Consumer
import androidx.window.layout.adapter.WindowBackend
import kotlinx.coroutines.channels.awaitClose
@@ -26,7 +27,7 @@
/**
* An implementation of [WindowInfoTracker] that provides the [WindowLayoutInfo] and
- * [WindowMetrics] for the given [Activity].
+ * [WindowMetrics] for the given [Activity] or [UiContext].
*
* @param windowMetricsCalculator a helper to calculate the [WindowMetrics] for the [Activity].
* @param windowBackend a helper to provide the [WindowLayoutInfo].
@@ -37,9 +38,21 @@
) : WindowInfoTracker {
/**
- * A [Flow] of window layout changes in the current visual [Context].
- *
- * @see Activity.onAttachedToWindow
+ * A [Flow] of window layout changes in the current visual [UiContext]. A context has to be
+ * either an [Activity] or created with [Context#createWindowContext].
+ */
+ override fun windowLayoutInfo(@UiContext context: Context): Flow<WindowLayoutInfo> {
+ return callbackFlow {
+ val listener = Consumer { info: WindowLayoutInfo -> trySend(info) }
+ windowBackend.registerLayoutChangeCallback(context, Runnable::run, listener)
+ awaitClose {
+ windowBackend.unregisterLayoutChangeCallback(listener)
+ }
+ }
+ }
+
+ /**
+ * A [Flow] of window layout changes in the current visual [Activity].
*/
override fun windowLayoutInfo(activity: Activity): Flow<WindowLayoutInfo> {
return callbackFlow {
diff --git a/window/window/src/main/java/androidx/window/layout/WindowMetrics.kt b/window/window/src/main/java/androidx/window/layout/WindowMetrics.kt
index bbf6d69..449c566 100644
--- a/window/window/src/main/java/androidx/window/layout/WindowMetrics.kt
+++ b/window/window/src/main/java/androidx/window/layout/WindowMetrics.kt
@@ -19,7 +19,6 @@
import android.os.Build.VERSION_CODES
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
-import androidx.annotation.RestrictTo.Scope.TESTS
import androidx.core.view.WindowInsetsCompat
import androidx.window.core.Bounds
import androidx.window.core.ExperimentalWindowApi
@@ -42,7 +41,7 @@
* An internal constructor for [WindowMetrics]
* @suppress
*/
- @RestrictTo(TESTS)
+ @RestrictTo(RestrictTo.Scope.TESTS)
constructor(
bounds: Rect,
insets: WindowInsetsCompat = WindowInsetsCompat.Builder().build()
diff --git a/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculator.kt b/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculator.kt
index 0de1501..f7181bb 100644
--- a/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculator.kt
+++ b/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculator.kt
@@ -16,14 +16,20 @@
package androidx.window.layout
+import android.view.WindowMetrics as AndroidWindowMetrics
import android.app.Activity
+import android.content.Context
+import android.inputmethodservice.InputMethodService
import android.os.Build
import android.view.Display
+import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
+import androidx.annotation.UiContext
+import androidx.core.view.WindowInsetsCompat
import androidx.window.core.ExperimentalWindowApi
/**
- * An interface to calculate the [WindowMetrics] for an [Activity].
+ * An interface to calculate the [WindowMetrics] for an [Activity] or a [UiContext].
*/
interface WindowMetricsCalculator {
@@ -65,6 +71,24 @@
fun computeCurrentWindowMetrics(activity: Activity): WindowMetrics
/**
+ * Computes the size and position of the area the window would occupy with
+ * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+ * and any combination of flags that would allow the window to extend behind display cutouts.
+ *
+ * On [Build.VERSION_CODES.Q] and older, a [UiContext] is either an [Activity] or an
+ * [InputMethodService]. On [Build.VERSION_CODES.R] and newer, a [UiContext] can also be one
+ * created via the [Context.createWindowContext] APIs.
+ *
+ * @see [computeCurrentWindowMetrics]
+ * @throws NotImplementedError if not implemented. The default implementation from [getOrCreate]
+ * is guaranteed to implement this method.
+ */
+ fun computeCurrentWindowMetrics(@UiContext context: Context): WindowMetrics {
+ throw NotImplementedError("Must override computeCurrentWindowMetrics(context) and" +
+ " provide an implementation.")
+ }
+
+ /**
* Computes the maximum size and position of the area the window can expect with
* [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
* and any combination of flags that would allow the window to extend behind display cutouts.
@@ -76,6 +100,27 @@
*/
fun computeMaximumWindowMetrics(activity: Activity): WindowMetrics
+ /**
+ * Computes the maximum size and position of the area the window can expect with
+ * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+ * and any combination of flags that would allow the window to extend behind display cutouts.
+ *
+ * The value returned from this method will always match [Display.getRealSize] on
+ * [Android 10][Build.VERSION_CODES.Q] and below.
+ *
+ * On [Build.VERSION_CODES.Q] and older, a [UiContext] is either an [Activity] or an
+ * [InputMethodService]. On [Build.VERSION_CODES.R] and newer, a [UiContext] can also be one
+ * created via the [Context.createWindowContext] APIs.
+ *
+ * @see [computeMaximumWindowMetrics]
+ * @throws NotImplementedError if not implemented. The default implementation from [getOrCreate]
+ * is guaranteed to implement this method.
+ */
+ fun computeMaximumWindowMetrics(@UiContext context: Context): WindowMetrics {
+ throw NotImplementedError("Must override computeMaximumWindowMetrics(context) and" +
+ " provide an implementation.")
+ }
+
companion object {
private var decorator: (WindowMetricsCalculator) -> WindowMetricsCalculator =
@@ -99,6 +144,18 @@
fun reset() {
decorator = { it }
}
+
+ /**
+ * Converts [Android API WindowMetrics][AndroidWindowMetrics] to
+ * [Jetpack version WindowMetrics][WindowMetrics]
+ */
+ @Suppress("ClassVerificationFailure")
+ @RequiresApi(Build.VERSION_CODES.R)
+ internal fun translateWindowMetrics(windowMetrics: AndroidWindowMetrics): WindowMetrics =
+ WindowMetrics(
+ windowMetrics.bounds,
+ WindowInsetsCompat.toWindowInsetsCompat(windowMetrics.windowInsets)
+ )
}
}
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 4fa733a..72e8ffa 100644
--- a/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculatorCompat.kt
+++ b/window/window/src/main/java/androidx/window/layout/WindowMetricsCalculatorCompat.kt
@@ -21,19 +21,24 @@
import android.content.res.Configuration
import android.graphics.Point
import android.graphics.Rect
+import android.inputmethodservice.InputMethodService
import android.os.Build
import android.os.Build.VERSION_CODES
import android.util.Log
import android.view.Display
import android.view.DisplayCutout
+import android.view.WindowManager
import androidx.annotation.RequiresApi
+import androidx.annotation.UiContext
import androidx.annotation.VisibleForTesting
import androidx.core.view.WindowInsetsCompat
import androidx.window.core.Bounds
import androidx.window.layout.util.ActivityCompatHelperApi24.isInMultiWindowMode
-import androidx.window.layout.util.ActivityCompatHelperApi30.currentWindowBounds
-import androidx.window.layout.util.ActivityCompatHelperApi30.currentWindowInsets
-import androidx.window.layout.util.ActivityCompatHelperApi30.maximumWindowBounds
+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
@@ -49,6 +54,42 @@
private val TAG: String = WindowMetricsCalculatorCompat::class.java.simpleName
/**
+ * Computes the current [WindowMetrics] for a given [Context]. The context can be either
+ * an [Activity], a Context created with [Context#createWindowContext], or an
+ * [InputMethodService].
+ * @see WindowMetricsCalculator.computeCurrentWindowMetrics
+ */
+ override fun computeCurrentWindowMetrics(@UiContext context: Context): WindowMetrics {
+ // TODO(b/259148796): Make WindowMetricsCalculatorCompat more testable
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.R) {
+ return currentWindowMetrics(context)
+ } else {
+ when (unwrapUiContext(context)) {
+ is Activity -> {
+ return computeCurrentWindowMetrics(context as Activity)
+ }
+ is InputMethodService -> {
+ val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+
+ // On older SDK levels, the app and IME could show up on different displays.
+ // However, there isn't a way for us to figure this out from the application
+ // layer. But, this should be good enough for now given the small likelihood of
+ // IMEs showing up on non-primary displays on these SDK levels.
+ @Suppress("DEPRECATION")
+ val displaySize = getRealSizeForDisplay(wm.defaultDisplay)
+
+ // IME occupies the whole display bounds.
+ val imeBounds = Rect(0, 0, displaySize.x, displaySize.y)
+ return WindowMetrics(imeBounds)
+ }
+ else -> {
+ throw IllegalArgumentException("$context is not a UiContext")
+ }
+ }
+ }
+ }
+
+ /**
* Computes the current [WindowMetrics] for a given [Activity]
* @see WindowMetricsCalculator.computeCurrentWindowMetrics
*/
@@ -78,19 +119,30 @@
* @see WindowMetricsCalculator.computeMaximumWindowMetrics
*/
override fun computeMaximumWindowMetrics(activity: Activity): WindowMetrics {
+ return computeMaximumWindowMetrics(activity as Context)
+ }
+
+ /**
+ * Computes the maximum [WindowMetrics] for a given [UiContext]
+ * @See WindowMetricsCalculator.computeMaximumWindowMetrics
+ */
+ override fun computeMaximumWindowMetrics(@UiContext context: Context): WindowMetrics {
+ // TODO(b/259148796): Make WindowMetricsCalculatorCompat more testable
val bounds = if (Build.VERSION.SDK_INT >= VERSION_CODES.R) {
- maximumWindowBounds(activity)
+ maximumWindowBounds(context)
} else {
+ val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
// [WindowManager#getDefaultDisplay] is deprecated but we have this for
- // compatibility with older versions
+ // compatibility with older versions, as we can't reliably get the display associated
+ // with a Context through public APIs either.
@Suppress("DEPRECATION")
- val display = activity.windowManager.defaultDisplay
+ val display = wm.defaultDisplay
val displaySize = getRealSizeForDisplay(display)
Rect(0, 0, displaySize.x, displaySize.y)
}
// TODO (b/233899790): compute insets for other platform versions below R
val windowInsetsCompat = if (Build.VERSION.SDK_INT >= VERSION_CODES.R) {
- computeWindowInsetsCompat(activity)
+ computeWindowInsetsCompat(context)
} else {
WindowInsetsCompat.Builder().build()
}
@@ -408,13 +460,13 @@
)
/**
- * Computes the current [WindowInsetsCompat] for a given [Activity].
+ * Computes the current [WindowInsetsCompat] for a given [Context].
*/
@RequiresApi(VERSION_CODES.R)
- internal fun computeWindowInsetsCompat(activity: Activity): WindowInsetsCompat {
+ internal fun computeWindowInsetsCompat(@UiContext context: Context): WindowInsetsCompat {
val build = Build.VERSION.SDK_INT
val windowInsetsCompat = if (build >= VERSION_CODES.R) {
- currentWindowInsets(activity)
+ currentWindowInsets(context)
} else {
throw Exception("Incompatible SDK version")
}
diff --git a/window/window/src/main/java/androidx/window/layout/adapter/WindowBackend.kt b/window/window/src/main/java/androidx/window/layout/adapter/WindowBackend.kt
index d799f4a..3524cb1 100644
--- a/window/window/src/main/java/androidx/window/layout/adapter/WindowBackend.kt
+++ b/window/window/src/main/java/androidx/window/layout/adapter/WindowBackend.kt
@@ -16,27 +16,32 @@
package androidx.window.layout.adapter
import android.app.Activity
+import android.content.Context
import androidx.core.util.Consumer
import androidx.window.layout.WindowLayoutInfo
import java.util.concurrent.Executor
+import androidx.annotation.UiContext
/**
* Backing interface for [androidx.window.layout.WindowInfoTracker] instances that serve as the
* default information supplier.
*/
internal interface WindowBackend {
+
/**
- * Registers a callback for layout changes of the window for the supplied [Activity].
+ * Registers a callback for layout changes of the window for the supplied [UiContext].
* Must be called only after the it is attached to the window.
+ * The supplied [UiContext] should correspond to a window or an area on the screen. It must be
+ * either an [Activity] or a [UiContext] created with [Context#createWindowContext].
*/
fun registerLayoutChangeCallback(
- activity: Activity,
+ @UiContext context: Context,
executor: Executor,
callback: Consumer<WindowLayoutInfo>
)
/**
- * Unregisters a callback for window layout changes of the [Activity] window.
+ * Unregisters a callback for window layout changes.
*/
fun unregisterLayoutChangeCallback(callback: Consumer<WindowLayoutInfo>)
}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackend.kt b/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackend.kt
index 2c304b4..64e60f8 100644
--- a/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackend.kt
+++ b/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackend.kt
@@ -16,12 +16,17 @@
package androidx.window.layout.adapter.extensions
+import androidx.window.extensions.core.util.function.Consumer as OEMConsumer
import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
import android.app.Activity
+import android.content.Context
import androidx.annotation.GuardedBy
+import androidx.annotation.UiContext
import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer
import androidx.window.core.ConsumerAdapter
+import androidx.window.core.ExtensionsUtil
+import androidx.window.extensions.WindowExtensions
import androidx.window.extensions.layout.WindowLayoutComponent
import androidx.window.layout.WindowLayoutInfo
import androidx.window.layout.adapter.WindowBackend
@@ -32,8 +37,9 @@
/**
* A wrapper around [WindowLayoutComponent] that ensures
- * [WindowLayoutComponent.addWindowLayoutInfoListener] is called at most once per activity while
- * there are active listeners.
+ * [WindowLayoutComponent.addWindowLayoutInfoListener] is called at most once per context while
+ * there are active listeners. Context has to be an [Activity] or a [UiContext] created with
+ * [Context#createWindowContext] or InputMethodService.
*/
internal class ExtensionWindowLayoutInfoBackend(
private val component: WindowLayoutComponent,
@@ -42,72 +48,117 @@
private val extensionWindowBackendLock = ReentrantLock()
@GuardedBy("lock")
- private val activityToListeners = mutableMapOf<Activity, MulticastConsumer>()
+ private val contextToListeners = mutableMapOf<Context, MulticastConsumer>()
+
@GuardedBy("lock")
- private val listenerToActivity = mutableMapOf<Consumer<WindowLayoutInfo>, Activity>()
+ private val listenerToContext = mutableMapOf<Consumer<WindowLayoutInfo>, Context>()
+
@GuardedBy("lock")
private val consumerToToken = mutableMapOf<MulticastConsumer, ConsumerAdapter.Subscription>()
/**
+ * The mapping from [MulticastConsumer] to Extensions Core version [Consumer]. This is used
+ * to translate [MulticastConsumer] to Extensions APIs after
+ * [WindowExtensions.VENDOR_API_LEVEL_2].
+ *
+ * @see WindowLayoutComponent.addWindowLayoutInfoListener
+ * @see WindowLayoutComponent.removeWindowLayoutInfoListener
+ */
+ @GuardedBy("lock")
+ private val consumerToOemConsumer =
+ mutableMapOf<MulticastConsumer, OEMConsumer<OEMWindowLayoutInfo>>()
+
+ /**
* Registers a listener to consume new values of [WindowLayoutInfo]. If there was a listener
- * registered for a given [Activity] then the new listener will receive a replay of the last
+ * registered for a given [Context] then the new listener will receive a replay of the last
* known value.
- * @param activity the host of a [android.view.Window]
+ * @param context the host of a [android.view.Window] or an area on the screen. Has to be an
+ * [Activity] or a [UiContext] created with [Context#createWindowContext] or InputMethodService.
* @param executor an executor from the parent interface
* @param callback the listener that will receive new values
*/
+ @OptIn(androidx.window.core.ExperimentalWindowApi::class)
override fun registerLayoutChangeCallback(
- activity: Activity,
+ @UiContext context: Context,
executor: Executor,
callback: Consumer<WindowLayoutInfo>
) {
extensionWindowBackendLock.withLock {
- activityToListeners[activity]?.let { listener ->
+ contextToListeners[context]?.let { listener ->
listener.addListener(callback)
- listenerToActivity[callback] = activity
+ listenerToContext[callback] = context
} ?: run {
- val consumer = MulticastConsumer(activity)
- activityToListeners[activity] = consumer
- listenerToActivity[callback] = activity
+ val consumer = MulticastConsumer(context)
+ contextToListeners[context] = consumer
+ listenerToContext[callback] = context
consumer.addListener(callback)
- val disposableToken = consumerAdapter.createSubscription(
- component,
- OEMWindowLayoutInfo::class,
- "addWindowLayoutInfoListener",
- "removeWindowLayoutInfoListener",
- activity
- ) { value ->
- consumer.accept(value)
+ if (ExtensionsUtil.safeVendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+ val consumeWindowLayoutInfo: (OEMWindowLayoutInfo) -> Unit = { value ->
+ consumer.accept(value)
+ }
+ // The registrations above maintain 1-many mapping of Context-Listeners across
+ // different subscription implementations.
+ val disposableToken = if (context is Activity) {
+ consumerAdapter.createSubscription(
+ component,
+ OEMWindowLayoutInfo::class,
+ "addWindowLayoutInfoListener",
+ "removeWindowLayoutInfoListener",
+ context,
+ consumeWindowLayoutInfo
+ )
+ } else {
+ // Prior to WM Extensions v2 addWindowLayoutInfoListener only
+ // supports Activities. Return empty WindowLayoutInfo if the
+ // provided Context is not an Activity.
+ consumer.accept(OEMWindowLayoutInfo(emptyList()))
+ return@registerLayoutChangeCallback
+ }
+ consumerToToken[consumer] = disposableToken
+ } else {
+ val oemConsumer = OEMConsumer<OEMWindowLayoutInfo> { info ->
+ consumer.accept(info)
+ }
+ consumerToOemConsumer[consumer] = oemConsumer
+ component.addWindowLayoutInfoListener(context,
+ oemConsumer)
}
- consumerToToken[consumer] = disposableToken
}
}
}
/**
- * Unregisters a listener, if this is the last listener for an [Activity] then the listener is
+ * Unregisters a listener, if this is the last listener for a [UiContext] then the listener is
* removed from the [WindowLayoutComponent]. Calling with the same listener multiple times in a
- * row does not have an effect. @param callback a listener that may have been registered
+ * row does not have an effect.
+ * @param callback a listener that may have been registered
*/
override fun unregisterLayoutChangeCallback(callback: Consumer<WindowLayoutInfo>) {
extensionWindowBackendLock.withLock {
- val activity = listenerToActivity[callback] ?: return
- val multicastListener = activityToListeners[activity] ?: return
+ val context = listenerToContext[callback] ?: return
+ val multicastListener = contextToListeners[context] ?: return
multicastListener.removeListener(callback)
- listenerToActivity.remove(callback)
+ listenerToContext.remove(callback)
if (multicastListener.isEmpty()) {
- consumerToToken.remove(multicastListener)?.dispose()
- activityToListeners.remove(activity)
+ contextToListeners.remove(context)
+ if (ExtensionsUtil.safeVendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+ consumerToToken.remove(multicastListener)?.dispose()
+ } else {
+ val oemConsumer = consumerToOemConsumer.remove(multicastListener)
+ if (oemConsumer != null) {
+ component.removeWindowLayoutInfoListener(oemConsumer)
+ }
+ }
}
}
}
/**
- * Returns {@code true} there is any registered listener information, {@code false} otherwise.
+ * Returns {@code true} if all the collections are empty, {@code false} otherwise
*/
@VisibleForTesting
fun hasRegisteredListeners(): Boolean {
- return !(activityToListeners.isEmpty() && listenerToActivity.isEmpty() &&
+ return !(contextToListeners.isEmpty() && listenerToContext.isEmpty() &&
consumerToToken.isEmpty())
}
@@ -117,7 +168,7 @@
* value whenever a new consumer registers.
*/
private class MulticastConsumer(
- private val activity: Activity
+ private val context: Context
) : Consumer<OEMWindowLayoutInfo> {
private val multicastConsumerLock = ReentrantLock()
@GuardedBy("lock")
@@ -127,7 +178,7 @@
override fun accept(value: OEMWindowLayoutInfo) {
multicastConsumerLock.withLock {
- lastKnownValue = translate(activity, value)
+ lastKnownValue = translate(context, value)
registeredListeners.forEach { consumer -> consumer.accept(lastKnownValue) }
}
}
diff --git a/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionsWindowLayoutInfoAdapter.kt b/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionsWindowLayoutInfoAdapter.kt
index 2ba93ca..ed9f74d 100644
--- a/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionsWindowLayoutInfoAdapter.kt
+++ b/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionsWindowLayoutInfoAdapter.kt
@@ -19,6 +19,9 @@
import androidx.window.extensions.layout.FoldingFeature as OEMFoldingFeature
import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
import android.app.Activity
+import android.content.Context
+import android.os.Build
+import androidx.annotation.UiContext
import androidx.window.core.Bounds
import androidx.window.layout.FoldingFeature
import androidx.window.layout.FoldingFeature.State.Companion.FLAT
@@ -27,11 +30,15 @@
import androidx.window.layout.HardwareFoldingFeature.Type.Companion.FOLD
import androidx.window.layout.HardwareFoldingFeature.Type.Companion.HINGE
import androidx.window.layout.WindowLayoutInfo
+import androidx.window.layout.WindowMetrics
import androidx.window.layout.WindowMetricsCalculatorCompat.computeCurrentWindowMetrics
internal object ExtensionsWindowLayoutInfoAdapter {
- internal fun translate(activity: Activity, oemFeature: OEMFoldingFeature): FoldingFeature? {
+ internal fun translate(
+ windowMetrics: WindowMetrics,
+ oemFeature: OEMFoldingFeature,
+ ): FoldingFeature? {
val type = when (oemFeature.type) {
OEMFoldingFeature.TYPE_FOLD -> FOLD
OEMFoldingFeature.TYPE_HINGE -> HINGE
@@ -43,17 +50,36 @@
else -> return null
}
val bounds = Bounds(oemFeature.bounds)
- return if (validBounds(activity, bounds)) {
+ return if (validBounds(windowMetrics, bounds)) {
HardwareFoldingFeature(Bounds(oemFeature.bounds), type, state)
} else {
null
}
}
- internal fun translate(activity: Activity, info: OEMWindowLayoutInfo): WindowLayoutInfo {
+ internal fun translate(
+ @UiContext context: Context,
+ info: OEMWindowLayoutInfo,
+ ): WindowLayoutInfo {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ translate(computeCurrentWindowMetrics(context), info)
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && (context is Activity)) {
+ translate(computeCurrentWindowMetrics(context), info)
+ } else {
+ throw UnsupportedOperationException(
+ "Display Features are only supported after Q. Display features for non-Activity " +
+ "contexts are not expected to be reported on devices running Q."
+ )
+ }
+ }
+
+ internal fun translate(
+ windowMetrics: WindowMetrics,
+ info: OEMWindowLayoutInfo
+ ): WindowLayoutInfo {
val features = info.displayFeatures.mapNotNull { feature ->
when (feature) {
- is OEMFoldingFeature -> translate(activity, feature)
+ is OEMFoldingFeature -> translate(windowMetrics, feature)
else -> null
}
}
@@ -61,19 +87,17 @@
}
/**
- * Validate the bounds for a [FoldingFeature] within a given [Activity]. Check the following
- * <ul>
- * <li>Bounds are not 0</li>
- * <li>Bounds are either full width or full height</li>
- * <li>Bounds do not take up the entire window</li>
- * </ul>
- *
- * @param activity housing the [FoldingFeature].
+ * Checks the bounds for a [FoldingFeature] within a given [WindowMetrics]. Validates the
+ * following:
+ * - [Bounds] are not `0`
+ * - [Bounds] are either full width or full height
+ * - [Bounds] do not take up the entire [windowMetrics]
+ * @param windowMetrics Extracted from a [UiContext] housing the [FoldingFeature].
* @param bounds the bounds of a [FoldingFeature]
- * @return true if the bounds are valid for the [Activity], false otherwise.
+ * @return true if the bounds are valid for the [WindowMetrics], false otherwise.
*/
- private fun validBounds(activity: Activity, bounds: Bounds): Boolean {
- val windowBounds = computeCurrentWindowMetrics(activity).bounds
+ private fun validBounds(windowMetrics: WindowMetrics, bounds: Bounds): Boolean {
+ val windowBounds = windowMetrics.bounds
if (bounds.isZero) {
return false
}
diff --git a/window/window/src/main/java/androidx/window/layout/adapter/sidecar/SidecarWindowBackend.kt b/window/window/src/main/java/androidx/window/layout/adapter/sidecar/SidecarWindowBackend.kt
index fa9f015..79fd244 100644
--- a/window/window/src/main/java/androidx/window/layout/adapter/sidecar/SidecarWindowBackend.kt
+++ b/window/window/src/main/java/androidx/window/layout/adapter/sidecar/SidecarWindowBackend.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.util.Log
import androidx.annotation.GuardedBy
+import androidx.annotation.UiContext
import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer
import androidx.window.core.Version
@@ -54,37 +55,43 @@
}
override fun registerLayoutChangeCallback(
- activity: Activity,
+ @UiContext context: Context,
executor: Executor,
callback: Consumer<WindowLayoutInfo>
) {
- globalLock.withLock {
- val windowExtension = windowExtension
- if (windowExtension == null) {
- if (DEBUG) {
- Log.v(TAG, "Extension not loaded, skipping callback registration.")
+ val activity = context as? Activity
+ activity?.let {
+ globalLock.withLock {
+ val windowExtension = windowExtension
+ if (windowExtension == null) {
+ if (DEBUG) {
+ Log.v(TAG, "Extension not loaded, skipping callback registration.")
+ }
+ callback.accept(WindowLayoutInfo(emptyList()))
+ return
}
- callback.accept(WindowLayoutInfo(emptyList()))
- return
- }
- // Check if the activity was already registered, in case we need to report tracking of
- // a new activity to the extension.
- val isActivityRegistered = isActivityRegistered(activity)
- val callbackWrapper = WindowLayoutChangeCallbackWrapper(activity, executor, callback)
- windowLayoutChangeCallbacks.add(callbackWrapper)
- if (!isActivityRegistered) {
- windowExtension.onWindowLayoutChangeListenerAdded(activity)
- } else {
- // Latest info for the previously registered callback for activity
- // and send it to the new activity
- val lastInfo = windowLayoutChangeCallbacks.firstOrNull {
- activity == it.activity
- }?.lastInfo
- if (lastInfo != null) {
- callbackWrapper.accept(lastInfo)
+ // Check if the activity was already registered, in case we need to report tracking
+ // of a new activity to the extension.
+ val isActivityRegistered = isActivityRegistered(activity)
+ val callbackWrapper =
+ WindowLayoutChangeCallbackWrapper(activity, executor, callback)
+ windowLayoutChangeCallbacks.add(callbackWrapper)
+ if (!isActivityRegistered) {
+ windowExtension.onWindowLayoutChangeListenerAdded(activity)
+ } else {
+ // Latest info for the previously registered callback for activity
+ // and send it to the new activity
+ val lastInfo = windowLayoutChangeCallbacks.firstOrNull {
+ activity == it.activity
+ }?.lastInfo
+ if (lastInfo != null) {
+ callbackWrapper.accept(lastInfo)
+ }
}
}
+ } ?: run {
+ callback.accept(WindowLayoutInfo(emptyList()))
}
}
diff --git a/window/window-samples/src/main/java/androidx/window/sample/demos/DemoItem.kt b/window/window/src/main/java/androidx/window/layout/util/ActivityCompatHelperApi24.kt
similarity index 68%
copy from window/window-samples/src/main/java/androidx/window/sample/demos/DemoItem.kt
copy to window/window/src/main/java/androidx/window/layout/util/ActivityCompatHelperApi24.kt
index fb08562..a4117f0 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/demos/DemoItem.kt
+++ b/window/window/src/main/java/androidx/window/layout/util/ActivityCompatHelperApi24.kt
@@ -13,8 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.window.sample.demos
+
+package androidx.window.layout.util
import android.app.Activity
+import android.os.Build
+import androidx.annotation.RequiresApi
-class DemoItem(val description: String, val buttonTitle: String, val clazz: Class<out Activity>)
\ No newline at end of file
+@RequiresApi(Build.VERSION_CODES.N)
+internal object ActivityCompatHelperApi24 {
+ fun isInMultiWindowMode(activity: Activity): Boolean {
+ return activity.isInMultiWindowMode
+ }
+}
diff --git a/window/window/src/main/java/androidx/window/layout/util/ActivityCompatHelper.kt b/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
similarity index 61%
rename from window/window/src/main/java/androidx/window/layout/util/ActivityCompatHelper.kt
rename to window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
index aafd1f9..eaafa91 100644
--- a/window/window/src/main/java/androidx/window/layout/util/ActivityCompatHelper.kt
+++ b/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -16,28 +16,26 @@
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
-@RequiresApi(Build.VERSION_CODES.N)
-internal object ActivityCompatHelperApi24 {
- fun isInMultiWindowMode(activity: Activity): Boolean {
- return activity.isInMultiWindowMode
- }
-}
-
@RequiresApi(Build.VERSION_CODES.R)
-internal object ActivityCompatHelperApi30 {
- fun currentWindowBounds(activity: Activity): Rect {
- return activity.windowManager.currentWindowMetrics.bounds
+internal object ContextCompatHelper {
+
+ fun currentWindowBounds(@UiContext context: Context): Rect {
+ val wm = context.getSystemService(WindowManager::class.java)
+ return wm.currentWindowMetrics.bounds
}
- fun maximumWindowBounds(activity: Activity): Rect {
- return activity.windowManager.maximumWindowMetrics.bounds
+ fun maximumWindowBounds(@UiContext context: Context): Rect {
+ val wm = context.getSystemService(WindowManager::class.java)
+ return wm.maximumWindowMetrics.bounds
}
/**
@@ -48,8 +46,9 @@
* @see androidx.window.layout.WindowMetrics.getWindowInsets
*/
@DoNotInline
- fun currentWindowInsets(activity: Activity): WindowInsetsCompat {
- val platformInsets = activity.windowManager.currentWindowMetrics.windowInsets
+ fun currentWindowInsets(@UiContext context: Context): WindowInsetsCompat {
+ val wm = context.getSystemService(WindowManager::class.java)
+ val platformInsets = wm.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
new file mode 100644
index 0000000..070bdf4
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/layout/util/ContextUtils.kt
@@ -0,0 +1,54 @@
+/*
+ * 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/main/res/values/attrs.xml b/window/window/src/main/res/values/attrs.xml
index 96527f8..d4af572 100644
--- a/window/window/src/main/res/values/attrs.xml
+++ b/window/window/src/main/res/values/attrs.xml
@@ -14,134 +14,191 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
- <!-- Defines what part of the width should be given to the primary activity. Defaults to an
- equal width split. -->
+<resources xmlns:xs="http://schemas.android.com/apk/res/android">
+ <!-- Defines what part of the Task bounds should be given to the primary container in split.
+ Values in range `(0.0, 1.0)` define the size of the primary container of the split relative
+ to the corresponding parent Task bounds.
+ The default is `0.5`, which is to split with equal width. -->
<attr name="splitRatio" format="float" />
- <!-- The smallest value of width of the parent window when the split should be used. -->
+ <!-- The smallest value of width of the parent Task bounds when the split should be used, in DP.
+ Uses `0` to always allow split regardless of the parent Task width.
+ The default is `600`. -->
<attr name="splitMinWidthDp" format="integer" />
- <!-- The smallest value of the smallest-width (sw) of the parent window in any rotation when
- the split should be used. -->
+ <!-- The smallest value of height of the parent Task bounds when the split should be used, in
+ DP.
+ Uses `0` to always allow split regardless of the parent Task height.
+ The default is `600`. -->
+ <attr name="splitMinHeightDp" format="integer" />
+ <!-- The smallest value of the smallest possible width of the parent Task bounds in any rotation
+ when the split should be used, in DP.
+ Uses `0` to always allow split regardless of the parent Task smallest width.
+ The default is `600`. -->
<attr name="splitMinSmallestWidthDp" format="integer" />
<!-- The largest value of the aspect ratio, expressed as (height / width) in decimal form, of
- the parent window bounds in portrait when the split should be used.
- `0` or `alwaysAllow` means to always allow split in portrait.
- `-1` or `alwaysDisallow` means to always disallow split in portrait.
- Any other values less than 1 are invalid. -->
+ the parent Task bounds in portrait when the split should be used.
+ Uses `0` or `alwaysAllow` to always allow split in portrait.
+ Uses `-1` or `alwaysDisallow` to always disallow split in portrait.
+ Any other values less than `1` are invalid.
+ The default is `1.4`. -->
<attr name="splitMaxAspectRatioInPortrait" format="float">
- <!-- Special value to always allow split in portrait. -->
+ <!-- Special value to always allow split in portrait regardless of the aspect ratio. -->
<enum name="alwaysAllow" value="0" />
- <!-- Special value to always disallow split in portrait. -->
+ <!-- Special value to always disallow split in portrait regardless of the aspect ratio. -->
<enum name="alwaysDisallow" value="-1" />
</attr>
<!-- The largest value of the aspect ratio, expressed as (width / height) in decimal form, of
- the parent window bounds in landscape when the split should be used.
- `0` or `alwaysAllow` means to always allow split in landscape.
- `-1` or `alwaysDisallow` means to always disallow split in landscape.
- Any other values less than 1 are invalid. -->
+ the parent Task bounds in landscape when the split should be used.
+ Uses `0` or `alwaysAllow` to always allow split in landscape.
+ Uses `-1` or `alwaysDisallow` to always disallow split in landscape.
+ Any other values less than `1` are invalid.
+ The default is `alwaysAllow`. -->
<attr name="splitMaxAspectRatioInLandscape" format="float">
- <!-- Special value to always allow split in landscape. -->
+ <!-- Special value to always allow split in landscape regardless of the aspect ratio. -->
<enum name="alwaysAllow" value="0" />
- <!-- Special value to always disallow split in landscape. -->
+ <!-- Special value to always disallow split in landscape regardless of the aspect ratio. -->
<enum name="alwaysDisallow" value="-1" />
</attr>
- <!-- The layout direction for the split. The value must be one of "ltr", "rtl" or "locale". -->
+ <!-- The layout direction for the split. -->
<attr name="splitLayoutDirection" format="enum">
- <!-- It splits the task bounds vertically, and the direction is deduced from the default
- language script of locale, which can be either "ltr" or "rtl". -->
- <enum name="locale" value="3" />
- <!-- It splits the task bounds vertically, and put the primary container on the left
- portion, and the secondary container on the right portion. -->
- <enum name="ltr" value="0" />
- <!-- It splits the task bounds vertically, and put the primary container on the right
- portion, and the secondary container on the left portion. -->
- <enum name="rtl" value="1" />
+ <!-- A layout direction that splits the task bounds vertically, and the direction is deduced
+ from the language script of locale. The direction can be either `rtl` or `ltr` -->
+ <enum name="locale" value="0" />
+ <!-- The primary container is placed on the left, and the secondary is on the right hand
+ side. -->
+ <enum name="ltr" value="1" />
+ <!-- The primary container is placed on the right, and the secondary is on the left hand
+ side. -->
+ <enum name="rtl" value="2" />
+ <!-- The primary container is placed on top, and the secondary is at the bottom. -->
+ <enum name="topToBottom" value="3" />
+ <!-- The primary container is placed on bottom, and the secondary is at the top. -->
+ <enum name="bottomToTop" value="4" />
</attr>
+ <!-- Determines what happens with the primary container when all activities are finished in
+ the associated secondary container.
+ The default is `never`. -->
<attr name="finishPrimaryWithSecondary" format="enum">
+ <!-- Never finish the primary container. -->
<enum name="never" value="0" />
+ <!-- Always finish the primary container. -->
<enum name="always" value="1" />
+ <!-- Only finish the primary container when the primary container is displayed adjacent to
+ the secondary container.
+ Does not finish the primary one when the secondary container is stacked on top of
+ the primary. -->
<enum name="adjacent" value="2" />
</attr>
- <attr name="finishPrimaryWithPlaceholder" format="enum">
- <enum name="always" value="1" />
- <enum name="adjacent" value="2" />
- </attr>
+ <!-- Determines what happens with the secondary container when all activities are finished
+ in the associated primary container.
+ The default is `always`. -->
<attr name="finishSecondaryWithPrimary" format="enum">
+ <!-- Never finish the secondary container. -->
<enum name="never" value="0" />
+ <!-- Always finish the secondary container. -->
<enum name="always" value="1" />
+ <!-- Only finish the secondary container when the primary container is displayed adjacent to
+ the secondary container.
+ Does not finish the secondary one when the secondary container is stacked on top of
+ the primary. -->
<enum name="adjacent" value="2" />
</attr>
+ <!-- Determines what happens with the primary container when the associated placeholder
+ activity is being finished.
+ The default is `always`. -->
+ <attr name="finishPrimaryWithPlaceholder" format="enum">
+ <!-- Always finish the primary container. -->
+ <enum name="always" value="1" />
+ <!-- Only finish the primary container when the primary container is displayed adjacent to
+ the placeholder container.
+ Does not finish the primary one when the placeholder container is stacked on top of
+ the primary. -->
+ <enum name="adjacent" value="2" />
+ </attr>
+ <!-- An optional but unique string to identify a `SplitPairRule`, `SplitPlaceholderRule` or
+ `ActivityRule`. The suggested usage is to set the tag to be able to differentiate between
+ different rules in the callbacks.
+ For example, it can be used to compute the right `SplitAttributes` for the given split rule
+ in `SplitAttributesCalculator.computeSplitAttributesForParams`.-->
+ <attr name="tag" format="string" />
+ <!-- Background color of animation if the animation requires a background.
+ The default is to use the theme's windowBackground. -->
+ <attr name="animationBackgroundColor" format="color" />
- <!-- Split configuration rules for activity pairs. Must contain at least one SplitPairFilter.
- See androidx.window.embedding.SplitPairRule for complete documentation. -->
- <declare-styleable name="SplitPairRule">
- <!-- When all activities are finished in the secondary container, the activity in the
- primary container that created the split should also be finished. Defaults to "never". -->
- <attr name="finishPrimaryWithSecondary" />
- <!-- When all activities are finished in the primary container, the activities in the
- secondary container in the split should also be finished. Defaults to "always". -->
- <attr name="finishSecondaryWithPrimary" />
- <!-- If there is an existing split with the same primary container, indicates whether the
- existing secondary container on top and all activities in it should be destroyed when a new
- split is created using this rule. Otherwise the new secondary will appear on top. Defaults
- to "false". -->
- <attr name="clearTop" format="boolean" />
- <attr name="splitRatio"/>
- <attr name="splitMinWidthDp"/>
- <attr name="splitMinSmallestWidthDp"/>
- <attr name="splitMaxAspectRatioInPortrait" />
- <attr name="splitMaxAspectRatioInLandscape" />
- <attr name="splitLayoutDirection"/>
- </declare-styleable>
-
- <!-- Configuration rules for split placeholders. Must contain at least one ActivityFilter for
- the primary activity for which the rule should be applied.
- See androidx.window.embedding.SplitPlaceholderRule for complete documentation. -->
- <declare-styleable name="SplitPlaceholderRule">
- <!-- Component name of the placeholder activity in the split. Must be non-empty. -->
- <attr name="placeholderActivityName" format="string" />
- <!-- Determines whether the placeholder will show on top in a smaller window size after
- it first appeared in a split with sufficient minimum width. -->
- <attr name="stickyPlaceholder" format="boolean" />
- <!-- When all activities are finished in the secondary container, the activity in the
- primary container that created the split should also be finished. Defaults to "always". -->
- <attr name="finishPrimaryWithPlaceholder" />
- <attr name="splitRatio"/>
- <attr name="splitMinWidthDp"/>
- <attr name="splitMinSmallestWidthDp"/>
- <attr name="splitMaxAspectRatioInPortrait" />
- <attr name="splitMaxAspectRatioInLandscape" />
- <attr name="splitLayoutDirection"/>
- </declare-styleable>
-
- <!-- Filter used to find if a pair of activities should be put in a split. -->
- <declare-styleable name="SplitPairFilter">
- <!-- Component name of the primary activity in the split. Must be non-empty. Can contain a
- wildcard at the end or instead of package name and/or class name. -->
- <attr name="primaryActivityName" format="string" />
- <!-- Component name of the secondary activity in the split. Must be non-empty. Can contain a
- wildcard at the end or instead of package name and/or class name. -->
- <attr name="secondaryActivityName" format="string" />
- <!-- Action used for secondary activity launch. May be empty. Must not contain wildcards.
+ <!-- Split configuration rules for activity pairs. Must contain at least one `SplitPairFilter`.
-->
- <attr name="secondaryActivityAction" format="string" />
+ <declare-styleable name="SplitPairRule">
+ <!-- If there is an existing split with the same primary container, indicates whether the
+ existing secondary container on top and all activities in it should be destroyed when a
+ new split is created using this rule. Otherwise the new secondary will appear on top.
+ The default is 'false'. -->
+ <attr name="clearTop" format="boolean" />
+ <attr name="finishPrimaryWithSecondary" />
+ <attr name="finishSecondaryWithPrimary" />
+ <attr name="splitRatio"/>
+ <attr name="splitMinWidthDp"/>
+ <attr name="splitMinHeightDp"/>
+ <attr name="splitMinSmallestWidthDp"/>
+ <attr name="splitMaxAspectRatioInPortrait" />
+ <attr name="splitMaxAspectRatioInLandscape" />
+ <attr name="splitLayoutDirection"/>
+ <attr name="tag"/>
+ <attr name="animationBackgroundColor"/>
+ </declare-styleable>
+
+ <!-- Configuration rules for split placeholders. Must contain at least one `ActivityFilter` for
+ the primary activity for which the rule should be applied. -->
+ <declare-styleable name="SplitPlaceholderRule">
+ <!-- Component name of the placeholder activity to launch in the split.
+ Must be non-empty. -->
+ <attr name="placeholderActivityName" format="string" />
+ <!-- Determines whether the placeholder will show on top if Task window constraints are not
+ satisfied after it first appeared in a split with sufficient Task window constraints.
+ The default is `false`. -->
+ <attr name="stickyPlaceholder" format="boolean" />
+ <attr name="finishPrimaryWithPlaceholder"/>
+ <attr name="splitRatio"/>
+ <attr name="splitMinWidthDp"/>
+ <attr name="splitMinHeightDp"/>
+ <attr name="splitMinSmallestWidthDp"/>
+ <attr name="splitMaxAspectRatioInPortrait" />
+ <attr name="splitMaxAspectRatioInLandscape" />
+ <attr name="splitLayoutDirection"/>
+ <attr name="tag"/>
+ <attr name="animationBackgroundColor"/>
</declare-styleable>
<!-- Layout configuration rules for individual activities. Takes precedence over
- SplitPairRule. Must contain at least one ActivityFilter.
- See androidx.window.layout.ActivityRule for complete documentation. -->
+ `SplitPairRule`. Must contain at least one `ActivityFilter`. -->
<declare-styleable name="ActivityRule">
- <!-- Whether the activity should always be expanded on launch. -->
+ <!-- Whether the activity should always be expanded on launch.
+ The default is `false`. -->
<attr name="alwaysExpand" format="boolean" />
+ <attr name="tag"/>
</declare-styleable>
- <!-- Filter for ActivityRule. -->
- <declare-styleable name="ActivityFilter">
+ <!-- Filter for `SplitPairRule` to find if a pair of activities should be put in a split. -->
+ <declare-styleable name="SplitPairFilter">
<!-- Component name of the primary activity in the split. Must be non-empty. Can contain a
- single wildcard at the end. -->
+ wildcard at the end or instead of package name and/or class name. -->
+ <attr name="primaryActivityName" format="string" />
+ <!-- Component name of the secondary activity in the split. Must be non-empty. Can contain a
+ wildcard at the end or instead of package name and/or class name. -->
+ <attr name="secondaryActivityName" format="string" />
+ <!-- Action used for the secondary activity launch. May be empty. Must not contain
+ wildcards.
+ When it is set, the filter only matches if the secondary activity is launched with an
+ intent with the given action. -->
+ <attr name="secondaryActivityAction" format="string" />
+ </declare-styleable>
+
+ <!-- Filter for `ActivityRule` and `SplitPlaceholderRule`. -->
+ <declare-styleable name="ActivityFilter">
+ <!-- Component name of the launched activity. Must be non-empty. Can contain a single
+ wildcard at the end. -->
<attr name="activityName" format="string" />
- <!-- Action used for activity launch. May be empty. Must not contain wildcards.
- -->
+ <!-- Action used for the activity launch. May be empty. Must not contain wildcards.
+ When it is set, the filter only matches if the activity is launched with an intent with
+ the given action. -->
<attr name="activityAction" format="string" />
</declare-styleable>
</resources>
\ No newline at end of file
diff --git a/window/window/src/test/java/androidx/window/core/ConsumerAdapterTest.kt b/window/window/src/test/java/androidx/window/core/ConsumerAdapterTest.kt
index 57709d2..cc18d1e 100644
--- a/window/window/src/test/java/androidx/window/core/ConsumerAdapterTest.kt
+++ b/window/window/src/test/java/androidx/window/core/ConsumerAdapterTest.kt
@@ -18,6 +18,8 @@
import android.annotation.SuppressLint
import android.app.Activity
+import android.content.Context
+import android.os.Build
import org.mockito.kotlin.mock
import java.util.function.Consumer
import org.junit.Assert.assertEquals
@@ -38,7 +40,12 @@
}
@Suppress("UNUSED_PARAMETER")
- fun addConsumer(a: Activity, c: Consumer<String>) {
+ fun addConsumer(context: Context, c: Consumer<String>) {
+ consumers.add(c)
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun addConsumer(activity: Activity, c: Consumer<String>) {
consumers.add(c)
}
@@ -85,6 +92,29 @@
}
@Test
+ fun testSubscribeByReflectionForContext() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ // java.util.function.Consumer#accept is supported after N.
+ return
+ }
+ val values = mutableListOf<String>()
+ val context = mock<Context>()
+ adapter.createSubscription(
+ listenerInterface,
+ String::class,
+ "addConsumer",
+ "removeConsumer",
+ context
+ ) { s: String ->
+ values.add(s)
+ }
+
+ assertEquals(1, listenerInterface.consumers.size)
+ listenerInterface.consumers.first().accept(value)
+ assertEquals(listOf(value), values)
+ }
+
+ @Test
fun testDisposeSubscribe() {
val values = mutableListOf<String>()
val subscription = adapter.createSubscription(
@@ -102,6 +132,24 @@
}
@Test
+ fun testDisposeSubscribeForContext() {
+ val values = mutableListOf<String>()
+ val context = mock<Context>()
+ val subscription = adapter.createSubscription(
+ listenerInterface,
+ String::class,
+ "addConsumer",
+ "removeConsumer",
+ context
+ ) { s: String ->
+ values.add(s)
+ }
+ subscription.dispose()
+
+ assertTrue(listenerInterface.consumers.isEmpty())
+ }
+
+ @Test
fun testToStringAdd() {
val values = mutableListOf<String>()
val consumer: (String) -> Unit = { s: String -> values.add(s) }
diff --git a/window/window/src/test/java/androidx/window/embedding/EmbeddingCompatTest.kt b/window/window/src/test/java/androidx/window/embedding/EmbeddingCompatTest.kt
index 9e42ae6..aa9e3f1 100644
--- a/window/window/src/test/java/androidx/window/embedding/EmbeddingCompatTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/EmbeddingCompatTest.kt
@@ -16,9 +16,14 @@
package androidx.window.embedding
+import java.util.function.Consumer as JavaConsumer
+import androidx.window.extensions.embedding.SplitInfo as OEMSplitInfo
import android.app.Activity
import androidx.window.core.ConsumerAdapter
+import androidx.window.core.ExtensionsUtil
import androidx.window.core.PredicateAdapter
+import androidx.window.extensions.WindowExtensions.VENDOR_API_LEVEL_2
+import androidx.window.extensions.core.util.function.Consumer
import androidx.window.extensions.embedding.ActivityEmbeddingComponent
import org.junit.Test
import org.mockito.kotlin.any
@@ -28,6 +33,7 @@
class EmbeddingCompatTest {
private val component = mock<ActivityEmbeddingComponent>()
+ private val vendorApiLevel = ExtensionsUtil.safeVendorApiLevel
private val embeddingCompat = EmbeddingCompat(
component,
EMBEDDING_ADAPTER,
@@ -35,6 +41,7 @@
mock()
)
+ @Suppress("Deprecation")
@Test
fun setSplitInfoCallback_callsActualMethod() {
val callback = object : EmbeddingInterfaceCompat.EmbeddingCallbackInterface {
@@ -43,7 +50,11 @@
}
embeddingCompat.setEmbeddingCallback(callback)
- verify(component).setSplitInfoCallback(any())
+ if (vendorApiLevel < VENDOR_API_LEVEL_2) {
+ verify(component).setSplitInfoCallback(any<JavaConsumer<List<OEMSplitInfo>>>())
+ } else {
+ verify(component).setSplitInfoCallback(any<Consumer<List<OEMSplitInfo>>>())
+ }
}
@Test
diff --git a/window/window/src/test/java/androidx/window/embedding/SplitAttributesTest.kt b/window/window/src/test/java/androidx/window/embedding/SplitAttributesTest.kt
new file mode 100644
index 0000000..96571cc
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/embedding/SplitAttributesTest.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.embedding
+
+import android.graphics.Color
+import androidx.window.core.WindowStrictModeException
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.BOTTOM_TO_TOP
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LEFT_TO_RIGHT
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LOCALE
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.RIGHT_TO_LEFT
+import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.TOP_TO_BOTTOM
+import androidx.window.embedding.SplitAttributes.SplitType
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertThrows
+import org.junit.Test
+
+/** Test class to verify [SplitAttributes] */
+class SplitAttributesTest {
+ @Test
+ fun testSplitAttributesEquals() {
+ val attrs1 = SplitAttributes.Builder()
+ .setSplitType(SplitType.splitEqually())
+ .setLayoutDirection(LOCALE)
+ .setAnimationBackgroundColor(0)
+ .build()
+ val attrs2 = SplitAttributes.Builder()
+ .setSplitType(SplitType.splitByHinge())
+ .setLayoutDirection(LOCALE)
+ .setAnimationBackgroundColor(0)
+ .build()
+ val attrs3 = SplitAttributes.Builder()
+ .setSplitType(SplitType.splitByHinge())
+ .setLayoutDirection(TOP_TO_BOTTOM)
+ .setAnimationBackgroundColor(0)
+ .build()
+ val attrs4 = SplitAttributes.Builder()
+ .setSplitType(SplitType.splitByHinge())
+ .setLayoutDirection(TOP_TO_BOTTOM)
+ .setAnimationBackgroundColor(Color.GREEN)
+ .build()
+ val attrs5 = SplitAttributes.Builder()
+ .setSplitType(SplitType.splitByHinge())
+ .setLayoutDirection(TOP_TO_BOTTOM)
+ .setAnimationBackgroundColor(Color.GREEN)
+ .build()
+
+ assertNotEquals(attrs1, attrs2)
+ assertNotEquals(attrs1.hashCode(), attrs2.hashCode())
+
+ assertNotEquals(attrs2, attrs3)
+ assertNotEquals(attrs2.hashCode(), attrs3.hashCode())
+
+ assertNotEquals(attrs3, attrs1)
+ assertNotEquals(attrs3.hashCode(), attrs1.hashCode())
+
+ assertNotEquals(attrs3, attrs4)
+ assertNotEquals(attrs3.hashCode(), attrs4.hashCode())
+
+ assertEquals(attrs4, attrs5)
+ assertEquals(attrs4.hashCode(), attrs5.hashCode())
+ }
+
+ @Test
+ fun testTypesEquals() {
+ val splitTypes = arrayOf(
+ SplitType.splitEqually(),
+ SplitType.expandContainers(),
+ SplitType.splitByHinge(),
+ SplitType.splitByHinge(SplitType.expandContainers())
+ )
+
+ for ((i, type1) in splitTypes.withIndex()) {
+ if (type1 is SplitType.RatioSplitType) {
+ assertEquals(
+ "Two SplitTypes must regarded as equal if their ratios are the same.",
+ type1, SplitType.ratio(type1.value)
+ )
+ assertEquals(type1.hashCode(), SplitType.ratio(type1.value).hashCode())
+ }
+ for ((j, type2) in splitTypes.withIndex()) {
+ if (i == j) {
+ assertEquals(type1, type2)
+ assertEquals(type1.hashCode(), type2.hashCode())
+ } else {
+ assertNotEquals(type1, type2)
+ assertNotEquals(type1.hashCode(), type2.hashCode())
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testSplitRatioRatio() {
+ assertThrows(WindowStrictModeException::class.java) {
+ SplitType.ratio(-0.01f)
+ }
+ assertThrows(WindowStrictModeException::class.java) {
+ SplitType.ratio(0.0f)
+ }
+ SplitType.ratio(0.001f)
+ SplitType.ratio(0.5f)
+ SplitType.ratio(0.999f)
+ assertThrows(WindowStrictModeException::class.java) {
+ SplitType.ratio(1.0f)
+ }
+ assertThrows(WindowStrictModeException::class.java) {
+ SplitType.ratio(1.1f)
+ }
+ }
+
+ @Test
+ fun testLayoutDirectionEquals() {
+ val layoutDirectionList = arrayOf(
+ LOCALE,
+ LEFT_TO_RIGHT,
+ RIGHT_TO_LEFT,
+ TOP_TO_BOTTOM,
+ BOTTOM_TO_TOP,
+ )
+
+ for ((i, layoutDirection1) in layoutDirectionList.withIndex()) {
+ for ((j, layoutDirection2) in layoutDirectionList.withIndex()) {
+ if (i == j) {
+ assertEquals(layoutDirection1, layoutDirection2)
+ assertEquals(layoutDirection1.hashCode(), layoutDirection2.hashCode())
+ } else {
+ assertNotEquals(layoutDirection1, layoutDirection2)
+ assertNotEquals(layoutDirection1.hashCode(), layoutDirection2.hashCode())
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
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 8f15b16..06c6bbc 100644
--- a/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt
@@ -29,7 +29,8 @@
val activity = mock<Activity>()
val firstStack = ActivityStack(listOf(activity))
val secondStack = ActivityStack(emptyList())
- val info = SplitInfo(firstStack, secondStack, 0.5f)
+ val attributes = SplitAttributes()
+ val info = SplitInfo(firstStack, secondStack, attributes)
assertTrue(info.contains(activity))
}
@@ -39,7 +40,8 @@
val activity = mock<Activity>()
val firstStack = ActivityStack(emptyList())
val secondStack = ActivityStack(listOf(activity))
- val info = SplitInfo(firstStack, secondStack, 0.5f)
+ val attributes = SplitAttributes()
+ val info = SplitInfo(firstStack, secondStack, attributes)
assertTrue(info.contains(activity))
}
@@ -49,8 +51,9 @@
val activity = mock<Activity>()
val firstStack = ActivityStack(emptyList())
val secondStack = ActivityStack(listOf(activity))
- val firstInfo = SplitInfo(firstStack, secondStack, 0.5f)
- val secondInfo = SplitInfo(firstStack, secondStack, 0.5f)
+ val attributes = SplitAttributes()
+ val firstInfo = SplitInfo(firstStack, secondStack, attributes)
+ val secondInfo = SplitInfo(firstStack, secondStack, attributes)
assertEquals(firstInfo, secondInfo)
assertEquals(firstInfo.hashCode(), secondInfo.hashCode())
diff --git a/window/window/src/test/java/androidx/window/layout/adapter/sidecar/SidecarWindowBackendUnitTest.kt b/window/window/src/test/java/androidx/window/layout/adapter/sidecar/SidecarWindowBackendUnitTest.kt
index a5d698f..f286aa1 100644
--- a/window/window/src/test/java/androidx/window/layout/adapter/sidecar/SidecarWindowBackendUnitTest.kt
+++ b/window/window/src/test/java/androidx/window/layout/adapter/sidecar/SidecarWindowBackendUnitTest.kt
@@ -73,6 +73,18 @@
}
@Test
+ public fun testRegisterLayoutChangeCallback_withContext() {
+ val backend = SidecarWindowBackend.getInstance(context)
+ backend.windowExtension = mock()
+
+ // Check registering the layout change callback
+ val consumer = mock<Consumer<WindowLayoutInfo>>()
+ val context = mock<Context>()
+ backend.registerLayoutChangeCallback(context, { obj: Runnable -> obj.run() }, consumer)
+ verify(consumer).accept(any())
+ }
+
+ @Test
public fun testRegisterLayoutChangeCallback_noExtension() {
val backend = SidecarWindowBackend.getInstance(context)
backend.windowExtension = null
diff --git a/window/window/src/testUtil/java/androidx/window/layout/TestWindowMetricsCalculator.kt b/window/window/src/testUtil/java/androidx/window/layout/TestWindowMetricsCalculator.kt
index 9ec07b1..5206e1e 100644
--- a/window/window/src/testUtil/java/androidx/window/layout/TestWindowMetricsCalculator.kt
+++ b/window/window/src/testUtil/java/androidx/window/layout/TestWindowMetricsCalculator.kt
@@ -16,7 +16,9 @@
package androidx.window.layout
import android.app.Activity
+import android.content.Context
import android.graphics.Rect
+import androidx.annotation.UiContext
/**
* Implementation of [WindowMetricsCalculator] for testing.
@@ -24,44 +26,62 @@
* @see WindowMetricsCalculator
*/
internal class TestWindowMetricsCalculator : WindowMetricsCalculator {
- private var globalOverriddenBounds: Rect? = null
- private val overriddenBounds = mutableMapOf<Activity, Rect?>()
- private val overriddenMaximumBounds = mutableMapOf<Activity, Rect?>()
+ private var overrideBounds: Rect? = null
+ private var overrideMaxBounds: Rect? = null
+ private val currentBounds = mutableMapOf<Context, Rect>()
+ private val maxBounds = mutableMapOf<Context, Rect>()
/**
- * Overrides the bounds returned from this helper for the given context. Passing `null` [bounds]
- * has the effect of clearing the bounds override.
+ * Sets the bounds returned from this helper for the given context.
*
- * Note: A global override set as a result of [.setCurrentBounds] takes precedence
- * over the value set with this method.
+ * Note: An override set via [setOverrideBounds] takes precedence over the values set with
+ * this method.
*/
- fun setCurrentBoundsForActivity(activity: Activity, bounds: Rect?) {
- overriddenBounds[activity] = bounds
+ fun setBounds(@UiContext context: Context, currentBounds: Rect, maxBounds: Rect) {
+ this.currentBounds[context] = currentBounds
+ this.maxBounds[context] = maxBounds
}
/**
- * Overrides the max bounds returned from this helper for the given context. Passing `null`
- * [bounds] has the effect of clearing the bounds override.
+ * Clears the bounds that were set via [setBounds] for the given context.
*/
- fun setMaximumBoundsForActivity(activity: Activity, bounds: Rect?) {
- overriddenMaximumBounds[activity] = bounds
+ fun clearBounds(@UiContext context: Context) {
+ currentBounds.remove(context)
+ maxBounds.remove(context)
}
/**
- * Overrides the bounds returned from this helper for all supplied contexts. Passing null
- * [bounds] has the effect of clearing the global override.
+ * Overrides the bounds returned from this helper for all supplied contexts.
*/
- fun setCurrentBounds(bounds: Rect?) {
- globalOverriddenBounds = bounds
+ fun setOverrideBounds(currentBounds: Rect, maxBounds: Rect) {
+ overrideBounds = currentBounds
+ overrideMaxBounds = maxBounds
+ }
+
+ /**
+ * Clears the overrides that were set in [setOverrideBounds].
+ */
+ fun clearOverrideBounds() {
+ overrideBounds = null
+ overrideMaxBounds = null
}
override fun computeCurrentWindowMetrics(activity: Activity): WindowMetrics {
- val bounds = globalOverriddenBounds ?: overriddenBounds[activity] ?: Rect()
+ return computeCurrentWindowMetrics(activity as Context)
+ }
+
+ override fun computeCurrentWindowMetrics(@UiContext context: Context): WindowMetrics {
+ val bounds = overrideBounds ?: currentBounds[context] ?: Rect()
return WindowMetrics(bounds)
}
override fun computeMaximumWindowMetrics(activity: Activity): WindowMetrics {
- return WindowMetrics(overriddenMaximumBounds[activity] ?: Rect())
+ return computeMaximumWindowMetrics(activity as Context)
+ }
+
+ override fun computeMaximumWindowMetrics(@UiContext context: Context): WindowMetrics {
+ val bounds = overrideMaxBounds ?: maxBounds[context] ?: Rect()
+ return WindowMetrics(bounds)
}
/**
@@ -69,8 +89,9 @@
* [.setCurrentBoundsForActivity].
*/
fun reset() {
- globalOverriddenBounds = null
- overriddenBounds.clear()
- overriddenMaximumBounds.clear()
+ overrideBounds = null
+ overrideMaxBounds = null
+ currentBounds.clear()
+ maxBounds.clear()
}
}