[go: nahoru, domu]

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>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
+     *     android:value="true|false"/&gt;
+     * &lt;/application&gt;
+     * </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 &mdash; Categorizes the split and specifies the sizes of the
+ *     primary and secondary activity containers relative to the parent bounds
+ *   - Layout direction &mdash; 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 &mdash; 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 &mdash; Primary container occupies half of the parent
+             *   window; secondary container, the other half
+             * - &gt; 0.5 &mdash; Primary container occupies a larger proportion
+             *   of the parent window than the secondary container
+             * - &lt; 0.5 &mdash; 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()
     }
 }