[go: nahoru, domu]

Migrate focus modifiers to Modifier.Node

This CL is a result of squashing all the commits
from the topic:
https://android-review.googlesource.com/q/topic:%22Migrate+focus+to+Modifer.Node%22

Here are some highlights of this CL
- FocusModifier is replaced by FocusTargetNode
- FocusProperties are implemented using Modifier.Node
- FocusEvents are powered by a backing FocusEventNode
- FocusRequesters find the focusTarget using the new
  Modifier.Node APIs
- KeyInputEvents and RotaryScrollEvents are routed using
  a common FocusAwareInputNode
- We use the Modifier.Node APIs to find the current value of
  The BeyondBoundsLayout modifier local that is needed when
  focus search is traversing through lazy lists.

Bug: 247708726
Bug: 255352203
Bug: 253043481
Bug: 247716483
Bug: 247708726
Bug: 254529934
Bug: 251840112
Bug: 251859987
Bug: 257141589
Test: ./gradlew compose:ui:ui:cC -P android.testInstrumentationRunnerArguments.package=androidx.compose.ui.focus
Relnote: FocusRequesterModifier is deprecated in favor of FocusRequesterNode
Change-Id: I7f4d7a99aa42f7f3e4f49d034f8358a41ed42d0f
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/FocusableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/FocusableTest.kt
index c3eb0aa..6db15c3 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/FocusableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/FocusableTest.kt
@@ -20,7 +20,11 @@
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.relocation.BringIntoViewResponder
 import androidx.compose.foundation.relocation.bringIntoViewResponder
 import androidx.compose.foundation.text.BasicText
@@ -33,6 +37,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
@@ -42,6 +47,7 @@
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsFocused
@@ -50,6 +56,7 @@
 import androidx.compose.ui.test.isNotFocusable
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -363,6 +370,7 @@
 
     @Test
     fun focusable_requestsBringIntoView_whenFocused() {
+        // Arrange.
         val requestedRects = mutableListOf<Rect?>()
         val bringIntoViewResponder = object : BringIntoViewResponder {
             override fun calculateRectForParent(localRect: Rect): Rect = localRect
@@ -387,13 +395,52 @@
 
         rule.runOnIdle {
             assertThat(requestedRects).isEmpty()
+        }
+
+        // Act.
+        rule.runOnIdle {
             focusRequester.requestFocus()
         }
 
+        // Assert.
         rule.runOnIdle {
             assertThat(requestedRects).containsExactly(Rect(Offset.Zero, Size(1f, 1f)))
         }
     }
+    // This test also verifies that the internal API autoInvalidateRemovedNode()
+    // is called when a modifier node is disposed.
+    @Test
+    fun removingFocusableFromLazyList_clearsFocus() {
+        // Arrange.
+        var lazyRowHasFocus = false
+        lateinit var state: LazyListState
+        lateinit var coroutineScope: CoroutineScope
+        var items by mutableStateOf((1..20).toList())
+        rule.setContent {
+            state = rememberLazyListState()
+            coroutineScope = rememberCoroutineScope()
+            LazyRow(
+                modifier = Modifier
+                    .requiredSize(100.dp)
+                    .onFocusChanged { lazyRowHasFocus = it.hasFocus },
+                state = state
+            ) {
+                items(items.size) {
+                    Box(Modifier.requiredSize(10.dp).testTag("$it").focusable())
+                }
+            }
+        }
+        rule.runOnIdle { coroutineScope.launch { state.scrollToItem(19) } }
+        rule.onNodeWithTag("19").performSemanticsAction(SemanticsActions.RequestFocus)
+
+        // Act.
+        rule.runOnIdle { items = (1..11).toList() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(lazyRowHasFocus).isFalse()
+        }
+    }
 
     @OptIn(ExperimentalFoundationApi::class)
     @Test
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
index 856f6b7..d1b621f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
@@ -39,7 +39,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.draw.DrawModifier
-import androidx.compose.ui.focus.FocusEventModifier
 import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.clipRect
@@ -181,7 +180,10 @@
     private val initialDelayMillis: Int,
     private val velocity: Dp,
     private val density: Density,
-) : Modifier.Element, LayoutModifier, DrawModifier, FocusEventModifier {
+) : Modifier.Element,
+    LayoutModifier,
+    DrawModifier,
+    @Suppress("DEPRECATION") androidx.compose.ui.focus.FocusEventModifier {
 
     private var contentWidth by mutableStateOf(0)
     private var containerWidth by mutableStateOf(0)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
index d292d7e..11bd819 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
@@ -165,6 +165,9 @@
         rule.runOnIdle {
             @OptIn(ExperimentalComposeUiApi::class)
             localInputModeManager!!.requestInputMode(InputMode.Keyboard)
+        }
+
+        rule.runOnIdle {
             focusRequester.requestFocus()
         }
 
diff --git a/compose/ui/ui-util/api/current.txt b/compose/ui/ui-util/api/current.txt
index b71072e..e80820a 100644
--- a/compose/ui/ui-util/api/current.txt
+++ b/compose/ui/ui-util/api/current.txt
@@ -20,6 +20,7 @@
     method public static inline <T> T? fastFirstOrNull(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
     method public static inline <T> void fastForEach(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action);
     method public static inline <T> void fastForEachIndexed(java.util.List<? extends T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+    method public static inline <T> void fastForEachReversed(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action);
     method public static inline <T, R> java.util.List<R> fastMap(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method public static inline <T, R, C extends java.util.Collection<? super R>> C fastMapTo(java.util.List<? extends T>, C destination, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method public static inline <T, R extends java.lang.Comparable<? super R>> T? fastMaxBy(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
diff --git a/compose/ui/ui-util/api/public_plus_experimental_current.txt b/compose/ui/ui-util/api/public_plus_experimental_current.txt
index b71072e..e80820a 100644
--- a/compose/ui/ui-util/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-util/api/public_plus_experimental_current.txt
@@ -20,6 +20,7 @@
     method public static inline <T> T? fastFirstOrNull(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
     method public static inline <T> void fastForEach(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action);
     method public static inline <T> void fastForEachIndexed(java.util.List<? extends T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+    method public static inline <T> void fastForEachReversed(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action);
     method public static inline <T, R> java.util.List<R> fastMap(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method public static inline <T, R, C extends java.util.Collection<? super R>> C fastMapTo(java.util.List<? extends T>, C destination, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method public static inline <T, R extends java.lang.Comparable<? super R>> T? fastMaxBy(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
diff --git a/compose/ui/ui-util/api/restricted_current.txt b/compose/ui/ui-util/api/restricted_current.txt
index b71072e..e80820a 100644
--- a/compose/ui/ui-util/api/restricted_current.txt
+++ b/compose/ui/ui-util/api/restricted_current.txt
@@ -20,6 +20,7 @@
     method public static inline <T> T? fastFirstOrNull(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
     method public static inline <T> void fastForEach(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action);
     method public static inline <T> void fastForEachIndexed(java.util.List<? extends T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+    method public static inline <T> void fastForEachReversed(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> action);
     method public static inline <T, R> java.util.List<R> fastMap(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method public static inline <T, R, C extends java.util.Collection<? super R>> C fastMapTo(java.util.List<? extends T>, C destination, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
     method public static inline <T, R extends java.lang.Comparable<? super R>> T? fastMaxBy(java.util.List<? extends T>, kotlin.jvm.functions.Function1<? super T,? extends R> selector);
diff --git a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
index 001b673..25b8c3c 100644
--- a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
+++ b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
@@ -38,6 +38,24 @@
 }
 
 /**
+ * Iterates through a [List] in reverse order using the index and calls [action] for each item.
+ * This does not allocate an iterator like [Iterable.forEach].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ */
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+inline fun <T> List<T>.fastForEachReversed(action: (T) -> Unit) {
+    contract { callsInPlace(action) }
+    for (index in indices.reversed()) {
+        val item = get(index)
+        action(item)
+    }
+}
+
+/**
  * Iterates through a [List] using the index and calls [action] for each item.
  * This does not allocate an iterator like [Iterable.forEachIndexed].
  *
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
index 42a92cf..b9629b2 100644
--- a/compose/ui/ui/api/current.ignore
+++ b/compose/ui/ui/api/current.ignore
@@ -1,3 +1,7 @@
 // Baseline format: 1.0
 InvalidNullConversion: androidx.compose.ui.graphics.GraphicsLayerModifierKt#graphicsLayer(androidx.compose.ui.Modifier, float, float, float, float, float, float, float, float, float, float, long, androidx.compose.ui.graphics.Shape, boolean, androidx.compose.ui.graphics.RenderEffect, long, long):
     Attempted to remove @NonNull annotation from method androidx.compose.ui.graphics.GraphicsLayerModifierKt.graphicsLayer(androidx.compose.ui.Modifier,float,float,float,float,float,float,float,float,float,float,long,androidx.compose.ui.graphics.Shape,boolean,androidx.compose.ui.graphics.RenderEffect,long,long)
+
+
+RemovedClass: androidx.compose.ui.focus.FocusManagerKt:
+    Removed class androidx.compose.ui.focus.FocusManagerKt
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 257bea7c..aa8aa59 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -292,8 +292,8 @@
     property public final int Up;
   }
 
-  @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
-    method public void onFocusEvent(androidx.compose.ui.focus.FocusState focusState);
+  @Deprecated @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
+    method @Deprecated public void onFocusEvent(androidx.compose.ui.focus.FocusState focusState);
   }
 
   public final class FocusEventModifierKt {
@@ -305,9 +305,6 @@
     method public boolean moveFocus(int focusDirection);
   }
 
-  public final class FocusManagerKt {
-  }
-
   public final class FocusModifierKt {
     method @Deprecated public static androidx.compose.ui.Modifier focusModifier(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier focusTarget(androidx.compose.ui.Modifier);
@@ -401,8 +398,8 @@
   public final class FocusRequesterKt {
   }
 
-  @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusRequesterModifier extends androidx.compose.ui.Modifier.Element {
-    method public androidx.compose.ui.focus.FocusRequester getFocusRequester();
+  @Deprecated @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusRequesterModifier extends androidx.compose.ui.Modifier.Element {
+    method @Deprecated public androidx.compose.ui.focus.FocusRequester getFocusRequester();
     property public abstract androidx.compose.ui.focus.FocusRequester focusRequester;
   }
 
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 8334cde..5199a29 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -388,22 +388,23 @@
     property public final int Up;
   }
 
-  @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
-    method public void onFocusEvent(androidx.compose.ui.focus.FocusState focusState);
+  @Deprecated @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
+    method @Deprecated public void onFocusEvent(androidx.compose.ui.focus.FocusState focusState);
   }
 
   public final class FocusEventModifierKt {
     method public static androidx.compose.ui.Modifier onFocusEvent(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusEvent);
   }
 
+  @androidx.compose.ui.ExperimentalComposeUiApi public interface FocusEventModifierNode extends androidx.compose.ui.node.DelegatableNode {
+    method public void onFocusEvent(androidx.compose.ui.focus.FocusState focusState);
+  }
+
   @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusManager {
     method public void clearFocus(optional boolean force);
     method public boolean moveFocus(int focusDirection);
   }
 
-  public final class FocusManagerKt {
-  }
-
   public final class FocusModifierKt {
     method @Deprecated public static androidx.compose.ui.Modifier focusModifier(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier focusTarget(androidx.compose.ui.Modifier);
@@ -487,6 +488,10 @@
     method public static androidx.compose.ui.Modifier focusProperties(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusProperties,kotlin.Unit> scope);
   }
 
+  @androidx.compose.ui.ExperimentalComposeUiApi public interface FocusPropertiesModifierNode extends androidx.compose.ui.node.DelegatableNode {
+    method public void modifyFocusProperties(androidx.compose.ui.focus.FocusProperties focusProperties);
+  }
+
   public final class FocusRequester {
     ctor public FocusRequester();
     method public boolean captureFocus();
@@ -526,13 +531,19 @@
   public final class FocusRequesterKt {
   }
 
-  @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusRequesterModifier extends androidx.compose.ui.Modifier.Element {
-    method public androidx.compose.ui.focus.FocusRequester getFocusRequester();
+  @Deprecated @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusRequesterModifier extends androidx.compose.ui.Modifier.Element {
+    method @Deprecated public androidx.compose.ui.focus.FocusRequester getFocusRequester();
     property public abstract androidx.compose.ui.focus.FocusRequester focusRequester;
   }
 
   public final class FocusRequesterModifierKt {
+    method @androidx.compose.ui.ExperimentalComposeUiApi public static boolean captureFocus(androidx.compose.ui.focus.FocusRequesterModifierNode);
     method public static androidx.compose.ui.Modifier focusRequester(androidx.compose.ui.Modifier, androidx.compose.ui.focus.FocusRequester focusRequester);
+    method @androidx.compose.ui.ExperimentalComposeUiApi public static boolean freeFocus(androidx.compose.ui.focus.FocusRequesterModifierNode);
+    method @androidx.compose.ui.ExperimentalComposeUiApi public static boolean requestFocus(androidx.compose.ui.focus.FocusRequesterModifierNode);
+  }
+
+  @androidx.compose.ui.ExperimentalComposeUiApi public interface FocusRequesterModifierNode extends androidx.compose.ui.node.DelegatableNode {
   }
 
   public interface FocusState {
@@ -544,6 +555,13 @@
     property public abstract boolean isFocused;
   }
 
+  @androidx.compose.ui.ExperimentalComposeUiApi public final class FocusTargetModifierNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.modifier.ModifierLocalNode androidx.compose.ui.node.ObserverNode {
+    ctor public FocusTargetModifierNode();
+    method public androidx.compose.ui.focus.FocusState getFocusState();
+    method public void onObservedReadsChanged();
+    property public final androidx.compose.ui.focus.FocusState focusState;
+  }
+
   public final class FocusTransactionsKt {
   }
 
@@ -1587,6 +1605,11 @@
     method public static androidx.compose.ui.Modifier onPreviewKeyEvent(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.key.KeyEvent,java.lang.Boolean> onPreviewKeyEvent);
   }
 
+  @androidx.compose.ui.ExperimentalComposeUiApi public interface KeyInputModifierNode extends androidx.compose.ui.node.DelegatableNode {
+    method public boolean onKeyEvent(android.view.KeyEvent event);
+    method public boolean onPreKeyEvent(android.view.KeyEvent event);
+  }
+
   public final class Key_androidKt {
     method public static long Key(int nativeKeyCode);
     method public static int getNativeKeyCode(long);
@@ -1932,6 +1955,11 @@
     method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier onRotaryScrollEvent(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.rotary.RotaryScrollEvent,java.lang.Boolean> onRotaryScrollEvent);
   }
 
+  @androidx.compose.ui.ExperimentalComposeUiApi public interface RotaryInputModifierNode extends androidx.compose.ui.node.DelegatableNode {
+    method public boolean onPreRotaryScrollEvent(androidx.compose.ui.input.rotary.RotaryScrollEvent event);
+    method public boolean onRotaryScrollEvent(androidx.compose.ui.input.rotary.RotaryScrollEvent event);
+  }
+
   @androidx.compose.ui.ExperimentalComposeUiApi public final class RotaryScrollEvent {
     method public float getHorizontalScrollPixels();
     method public long getUptimeMillis();
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
index 42a92cf..b9629b2 100644
--- a/compose/ui/ui/api/restricted_current.ignore
+++ b/compose/ui/ui/api/restricted_current.ignore
@@ -1,3 +1,7 @@
 // Baseline format: 1.0
 InvalidNullConversion: androidx.compose.ui.graphics.GraphicsLayerModifierKt#graphicsLayer(androidx.compose.ui.Modifier, float, float, float, float, float, float, float, float, float, float, long, androidx.compose.ui.graphics.Shape, boolean, androidx.compose.ui.graphics.RenderEffect, long, long):
     Attempted to remove @NonNull annotation from method androidx.compose.ui.graphics.GraphicsLayerModifierKt.graphicsLayer(androidx.compose.ui.Modifier,float,float,float,float,float,float,float,float,float,float,long,androidx.compose.ui.graphics.Shape,boolean,androidx.compose.ui.graphics.RenderEffect,long,long)
+
+
+RemovedClass: androidx.compose.ui.focus.FocusManagerKt:
+    Removed class androidx.compose.ui.focus.FocusManagerKt
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 4db7404..7d8ccd9 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -292,8 +292,8 @@
     property public final int Up;
   }
 
-  @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
-    method public void onFocusEvent(androidx.compose.ui.focus.FocusState focusState);
+  @Deprecated @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
+    method @Deprecated public void onFocusEvent(androidx.compose.ui.focus.FocusState focusState);
   }
 
   public final class FocusEventModifierKt {
@@ -305,9 +305,6 @@
     method public boolean moveFocus(int focusDirection);
   }
 
-  public final class FocusManagerKt {
-  }
-
   public final class FocusModifierKt {
     method @Deprecated public static androidx.compose.ui.Modifier focusModifier(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier focusTarget(androidx.compose.ui.Modifier);
@@ -401,8 +398,8 @@
   public final class FocusRequesterKt {
   }
 
-  @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusRequesterModifier extends androidx.compose.ui.Modifier.Element {
-    method public androidx.compose.ui.focus.FocusRequester getFocusRequester();
+  @Deprecated @kotlin.jvm.JvmDefaultWithCompatibility public interface FocusRequesterModifier extends androidx.compose.ui.Modifier.Element {
+    method @Deprecated public androidx.compose.ui.focus.FocusRequester getFocusRequester();
     property public abstract androidx.compose.ui.focus.FocusRequester focusRequester;
   }
 
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt
index d4c43cf..7c416f6 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ConditionalFocusabilityDemo.kt
@@ -81,8 +81,7 @@
         Row {
             var item2active by remember { mutableStateOf(false) }
             Text(
-                text = "focusable item that is " +
-                    "${if (item2active) "activated" else "deactivated"}",
+                text = "focusable item that is " + if (item2active) "activated" else "deactivated",
                 modifier = Modifier
                     .focusAwareBackground()
                     .focusRequester(item2)
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/LazyListChildFocusDemos.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/LazyListChildFocusDemos.kt
index 6760f3b..80c066f6 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/LazyListChildFocusDemos.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/LazyListChildFocusDemos.kt
@@ -28,16 +28,22 @@
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.FocusRequester.Companion.Default
+import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LocalPinnableContainer
+import androidx.compose.ui.layout.PinnableContainer
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.launch
@@ -84,15 +90,30 @@
             var previouslyFocusedItem: FocusRequester? by remember { mutableStateOf(null) }
             LazyRow(
                 Modifier
-                    .onFocusChanged { if (it.isFocused) previouslyFocusedItem?.requestFocus() }
-                    .then(previouslyFocusedItem?.let { Modifier.focusable() } ?: Modifier)
+                    .focusProperties {
+                        @OptIn(ExperimentalComposeUiApi::class)
+                        enter = { previouslyFocusedItem ?: Default }
+                    }
             ) {
-                items(10) {
-                    val focusRequester = remember { FocusRequester() }
+                items(10) { index ->
+                    val focusRequester = remember(index) { FocusRequester() }
+                    val pinnableContainer = LocalPinnableContainer.current
+                    var pinnedHandle: PinnableContainer.PinnedHandle? = null
                     FocusableBox(Modifier
-                        .onFocusChanged { if (it.isFocused) previouslyFocusedItem = focusRequester }
+                        .onFocusChanged {
+                            if (it.isFocused) {
+                                previouslyFocusedItem = focusRequester
+                                pinnedHandle = pinnableContainer?.pin()
+                            }
+                        }
                         .focusRequester(focusRequester)
                     )
+                    DisposableEffect(pinnableContainer) {
+                        onDispose {
+                            pinnedHandle?.unpin()
+                            pinnedHandle = null
+                        }
+                    }
                 }
             }
         }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CaptureFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CaptureFocusTest.kt
index 5e49f81..a70eb38 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CaptureFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CaptureFocusTest.kt
@@ -19,10 +19,6 @@
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusStateImpl.Active
-import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
-import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -47,9 +43,10 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Active))
+                    .focusTarget()
             )
         }
+        rule.runOnIdle { focusRequester.requestFocus() }
 
         // Act.
         val success = rule.runOnIdle {
@@ -68,14 +65,26 @@
     fun activeParent_captureFocus_retainsStateAsActiveParent() {
         // Arrange.
         lateinit var focusState: FocusState
+        val initialFocus = FocusRequester()
         val focusRequester = FocusRequester()
         rule.setFocusableContent {
             Box(
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(ActiveParent))
-            )
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .focusRequester(initialFocus)
+                        .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle {
+            initialFocus.requestFocus()
+            assertThat(focusState.isCaptured).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
         }
 
         // Act.
@@ -101,9 +110,14 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Captured))
+                    .focusTarget()
             )
         }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            focusRequester.captureFocus()
+            assertThat(focusState.isCaptured).isTrue()
+        }
 
         // Act.
         val success = rule.runOnIdle {
@@ -118,7 +132,7 @@
     }
 
     @Test
-    fun deactivated_captureFocus_retainsStateAsDeactivated() {
+    fun deactivated_captureFocus_retainsFocusState() {
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
@@ -142,12 +156,11 @@
             assertThat(success).isFalse()
             assertThat(focusState.isCaptured).isFalse()
             assertThat(focusState.isFocused).isFalse()
-            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 
     @Test
-    fun deactivatedParent_captureFocus_retainsStateAsDeactivatedParent() {
+    fun deactivatedParent_captureFocus_retainsFocusState() {
         // Arrange.
         lateinit var focusState: FocusState
         val initialFocus = FocusRequester()
@@ -179,7 +192,6 @@
             assertThat(success).isFalse()
             assertThat(focusState.isCaptured).isFalse()
             assertThat(focusState.hasFocus).isTrue()
-            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 
@@ -193,7 +205,7 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
         }
 
@@ -210,6 +222,3 @@
         }
     }
 }
-
-private val FocusState.isDeactivated: Boolean
-    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ClearFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ClearFocusTest.kt
deleted file mode 100644
index 52142f1..0000000
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ClearFocusTest.kt
+++ /dev/null
@@ -1,421 +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.compose.ui.focus
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.runtime.SideEffect
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusStateImpl.Active
-import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
-import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@SmallTest
-@RunWith(Parameterized::class)
-class ClearFocusTest(private val forced: Boolean) {
-    @get:Rule
-    val rule = createComposeRule()
-
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "forcedClear = {0}")
-        fun initParameters() = listOf(true, false)
-    }
-
-    @Test
-    fun active_isCleared() {
-        // Arrange.
-        val modifier = FocusModifier(Active)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(modifier))
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(cleared).isTrue()
-            assertThat(modifier.focusState).isEqualTo(Inactive)
-        }
-    }
-
-    @Test
-    fun active_isClearedAndRemovedFromParentsFocusedChild() {
-        // Arrange.
-        val parent = FocusModifier(ActiveParent)
-        val modifier = FocusModifier(Active)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(parent)) {
-                Box(Modifier.focusTarget(modifier))
-            }
-            SideEffect {
-                parent.focusedChild = modifier
-            }
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(cleared).isTrue()
-            assertThat(modifier.focusState).isEqualTo(Inactive)
-        }
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun activeParent_noFocusedChild_throwsException() {
-        // Arrange.
-        val modifier = FocusModifier(ActiveParent)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(modifier))
-        }
-
-        // Act.
-        rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-    }
-
-    @Test
-    fun activeParent_isClearedAndRemovedFromParentsFocusedChild() {
-        // Arrange.
-        val parent = FocusModifier(ActiveParent)
-        val modifier = FocusModifier(ActiveParent)
-        val child = FocusModifier(Active)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(parent)) {
-                Box(Modifier.focusTarget(modifier)) {
-                    Box(Modifier.focusTarget(child))
-                }
-            }
-            SideEffect {
-                parent.focusedChild = modifier
-                modifier.focusedChild = child
-            }
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(cleared).isTrue()
-            assertThat(modifier.focusedChild).isNull()
-            assertThat(modifier.focusState).isEqualTo(Inactive)
-        }
-    }
-
-    @Test
-    fun activeParent_clearsEntireHierarchy() {
-        // Arrange.
-        val modifier = FocusModifier(ActiveParent)
-        val child = FocusModifier(ActiveParent)
-        val grandchild = FocusModifier(ActiveParent)
-        val greatGrandchild = FocusModifier(Active)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(modifier)) {
-                Box(Modifier.focusTarget(child)) {
-                    Box(Modifier.focusTarget(grandchild)) {
-                        Box(Modifier.focusTarget(greatGrandchild))
-                    }
-                }
-            }
-            SideEffect {
-                modifier.focusedChild = child
-                child.focusedChild = grandchild
-                grandchild.focusedChild = greatGrandchild
-            }
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(cleared).isTrue()
-            assertThat(modifier.focusedChild).isNull()
-            assertThat(child.focusedChild).isNull()
-            assertThat(grandchild.focusedChild).isNull()
-            assertThat(modifier.focusState).isEqualTo(Inactive)
-            assertThat(child.focusState).isEqualTo(Inactive)
-            assertThat(grandchild.focusState).isEqualTo(Inactive)
-            assertThat(greatGrandchild.focusState).isEqualTo(Inactive)
-        }
-    }
-
-    @Test
-    fun captured_isCleared_whenForced() {
-        // Arrange.
-        val modifier = FocusModifier(Captured)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(modifier))
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            when (forced) {
-                true -> {
-                    assertThat(cleared).isTrue()
-                    assertThat(modifier.focusState).isEqualTo(Inactive)
-                }
-                false -> {
-                    assertThat(cleared).isFalse()
-                    assertThat(modifier.focusState).isEqualTo(Captured)
-                }
-            }
-        }
-    }
-
-    @Test
-    fun active_isClearedAndRemovedFromParentsFocusedChild_whenForced() {
-        // Arrange.
-        val parent = FocusModifier(ActiveParent)
-        val modifier = FocusModifier(Captured)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(parent)) {
-                Box(Modifier.focusTarget(modifier))
-            }
-            SideEffect {
-                parent.focusedChild = modifier
-            }
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            when (forced) {
-                true -> {
-                    assertThat(cleared).isTrue()
-                    assertThat(modifier.focusState).isEqualTo(Inactive)
-                }
-                false -> {
-                    assertThat(cleared).isFalse()
-                    assertThat(modifier.focusState).isEqualTo(Captured)
-                }
-            }
-        }
-    }
-
-    @Test
-    fun Inactive_isUnchanged() {
-        // Arrange.
-        val modifier = FocusModifier(Inactive)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(modifier))
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(cleared).isTrue()
-            assertThat(modifier.focusState).isEqualTo(Inactive)
-        }
-    }
-
-    @Test
-    fun Deactivated_isUnchanged() {
-        // Arrange.
-        val modifier = FocusModifier(Inactive)
-        rule.setFocusableContent {
-            Box(
-                Modifier
-                    .focusProperties { canFocus = false }
-                    .focusTarget(modifier)
-            )
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(cleared).isTrue()
-            assertThat(modifier.focusState.isDeactivated).isTrue()
-        }
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun deactivatedParent_noFocusedChild_throwsException() {
-        // Arrange.
-        val modifier = FocusModifier(DeactivatedParent)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(modifier))
-        }
-
-        // Act.
-        rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-    }
-
-    @Test
-    fun deactivatedParent_isClearedAndRemovedFromParentsFocusedChild() {
-        // Arrange.
-        val parent = FocusModifier(ActiveParent)
-        val modifier = FocusModifier(ActiveParent)
-        val child = FocusModifier(Active)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(parent)) {
-                Box(
-                    Modifier
-                        .focusProperties { canFocus = false }
-                        .focusTarget(modifier)
-                ) {
-                    Box(Modifier.focusTarget(child))
-                }
-            }
-            SideEffect {
-                parent.focusedChild = modifier
-                modifier.focusedChild = child
-            }
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(cleared).isTrue()
-            assertThat(modifier.focusedChild).isNull()
-            assertThat(modifier.focusState.isDeactivated).isTrue()
-        }
-    }
-
-    @Test
-    fun deactivatedParent_withDeactivatedGrandParent_isClearedAndRemovedFromParentsFocusedChild() {
-        // Arrange.
-        val parent = FocusModifier(ActiveParent)
-        val modifier = FocusModifier(ActiveParent)
-        val child = FocusModifier(Active)
-        rule.setFocusableContent {
-            Box(Modifier
-                .focusProperties { canFocus = false }
-                .focusTarget(parent)
-            ) {
-                Box(Modifier
-                    .focusProperties { canFocus = false }
-                    .focusTarget(modifier)
-                ) {
-                    Box(Modifier.focusTarget(child))
-                }
-            }
-            SideEffect {
-                parent.focusedChild = modifier
-                modifier.focusedChild = child
-            }
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(cleared).isTrue()
-            assertThat(modifier.focusedChild).isNull()
-            assertThat(modifier.focusState.isDeactivated).isTrue()
-        }
-    }
-
-    @Test
-    fun deactivatedParent_clearsEntireHierarchy() {
-        // Arrange.
-        val modifier = FocusModifier(ActiveParent)
-        val child = FocusModifier(ActiveParent)
-        val grandchild = FocusModifier(ActiveParent)
-        val greatGrandchild = FocusModifier(ActiveParent)
-        val greatGreatGrandchild = FocusModifier(Active)
-        rule.setFocusableContent {
-            Box(Modifier
-                .focusProperties { canFocus = false }
-                .focusTarget(modifier)
-            ) {
-                Box(modifier = Modifier.focusTarget(child)) {
-                    Box(Modifier
-                        .focusProperties { canFocus = false }
-                        .focusTarget(grandchild)
-                    ) {
-                        Box(Modifier.focusTarget(greatGrandchild)) {
-                            Box(Modifier.focusTarget(greatGreatGrandchild))
-                        }
-                    }
-                }
-            }
-            SideEffect {
-                modifier.focusedChild = child
-                child.focusedChild = grandchild
-                grandchild.focusedChild = greatGrandchild
-                greatGrandchild.focusedChild = greatGreatGrandchild
-            }
-        }
-
-        // Act.
-        val cleared = rule.runOnIdle {
-            modifier.clearFocus(forced)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(cleared).isTrue()
-            assertThat(modifier.focusedChild).isNull()
-            assertThat(child.focusedChild).isNull()
-            assertThat(grandchild.focusedChild).isNull()
-            assertThat(modifier.focusState).isEqualTo(Deactivated)
-            assertThat(child.focusState).isEqualTo(Inactive)
-            assertThat(grandchild.focusState).isEqualTo(Deactivated)
-            assertThat(greatGrandchild.focusState).isEqualTo(Inactive)
-            assertThat(greatGreatGrandchild.focusState).isEqualTo(Inactive)
-        }
-    }
-}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CombinedFocusModifierNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CombinedFocusModifierNodeTest.kt
new file mode 100644
index 0000000..721a3089
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CombinedFocusModifierNodeTest.kt
@@ -0,0 +1,245 @@
+/*
+ * 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.compose.ui.focus
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.modifierElementOf
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@OptIn(ExperimentalComposeUiApi::class)
+@MediumTest
+@RunWith(Parameterized::class)
+class CombinedFocusModifierNodeTest(private val delegatedFocusTarget: Boolean) {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun requestFocus() {
+        // Arrange.
+        val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
+        rule.setFocusableContent {
+            Box(Modifier.combinedFocusNode(combinedFocusNode))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            combinedFocusNode.requestFocus()
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(combinedFocusNode.focusState.isFocused).isTrue()
+        }
+    }
+
+    @Test
+    fun captureFocus() {
+        // Arrange.
+        val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
+        rule.setFocusableContent {
+            Box(Modifier.combinedFocusNode(combinedFocusNode))
+        }
+        rule.runOnIdle {
+            combinedFocusNode.requestFocus()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            combinedFocusNode.captureFocus()
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(combinedFocusNode.focusState.isFocused).isTrue()
+            assertThat(combinedFocusNode.focusState.isCaptured).isTrue()
+        }
+    }
+
+    @Test
+    fun freeFocus() {
+        // Arrange.
+        val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
+        rule.setFocusableContent {
+            Box(Modifier.combinedFocusNode(combinedFocusNode))
+        }
+        rule.runOnIdle {
+            combinedFocusNode.requestFocus()
+            combinedFocusNode.captureFocus()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            combinedFocusNode.freeFocus()
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(combinedFocusNode.focusState.isFocused).isTrue()
+            assertThat(combinedFocusNode.focusState.isCaptured).isFalse()
+        }
+    }
+
+    @Test
+    fun requestFocusWhenCanFocusIsTrue() {
+        // Arrange.
+        val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget).apply { canFocus = true }
+        rule.setFocusableContent {
+            Box(Modifier.combinedFocusNode(combinedFocusNode))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            combinedFocusNode.requestFocus()
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(combinedFocusNode.focusState.isFocused).isTrue()
+        }
+    }
+
+    @Test
+    fun requestFocusWhenCanFocusIsFalse() {
+        // Arrange.
+        val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget).apply { canFocus = false }
+        rule.setFocusableContent {
+            Box(Modifier.combinedFocusNode(combinedFocusNode))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            combinedFocusNode.requestFocus()
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(combinedFocusNode.focusState.isFocused).isFalse()
+        }
+    }
+
+    /**
+     * This test checks that [FocusPropertiesModifierNode.modifyFocusProperties] is called when a
+     * property changes.
+     */
+    @Test
+    fun losesFocusWhenCanFocusChangesToFalse() {
+        // Arrange.
+        val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
+        rule.setFocusableContent {
+            Box(Modifier.combinedFocusNode(combinedFocusNode))
+        }
+        rule.runOnIdle {
+            combinedFocusNode.requestFocus()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            combinedFocusNode.canFocus = false
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(combinedFocusNode.focusState.isFocused).isFalse()
+        }
+    }
+
+    @Test
+    fun doesNotGainFocusWhenCanFocusChangesToTrue() {
+        // Arrange.
+        val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
+        rule.setFocusableContent {
+            Box(Modifier.combinedFocusNode(combinedFocusNode))
+        }
+        rule.runOnIdle {
+            combinedFocusNode.requestFocus()
+            combinedFocusNode.canFocus = false
+        }
+
+        // Act.
+        rule.runOnIdle {
+            combinedFocusNode.canFocus = true
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(combinedFocusNode.focusState.isFocused).isFalse()
+        }
+    }
+
+    private fun Modifier.combinedFocusNode(combinedFocusNode: CombinedFocusNode): Modifier {
+        return this
+            .then(
+                modifierElementOf(
+                    key = combinedFocusNode,
+                    create = { combinedFocusNode },
+                    update = { it.focusState = combinedFocusNode.focusState },
+                    definitions = { name = "CombinedFocusNode" }
+                )
+            )
+            .then(if (delegatedFocusTarget) Modifier else Modifier.focusTarget())
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "delegatedFocusTarget = {0}")
+        fun initParameters() =
+            listOf(
+                false,
+                // TODO: Delegation does not work right now because a delegated node can
+                //  reference the node delegating to it, but it can't reference a delegated node in
+                //  its parent. For some use-cases, a parent needs to invalidate a child. We cannot
+                //  do this when the child is a delegated node.
+                // true
+            )
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    private class CombinedFocusNode(delegatedFocusTarget: Boolean) :
+        FocusRequesterModifierNode,
+        FocusEventModifierNode,
+        FocusPropertiesModifierNode,
+        DelegatingNode() {
+
+        init {
+            if (delegatedFocusTarget) delegated { FocusTargetModifierNode() }
+        }
+
+        lateinit var focusState: FocusState
+
+        var canFocus by mutableStateOf(true)
+
+        override fun onFocusEvent(focusState: FocusState) {
+            this.focusState = focusState
+        }
+
+        override fun modifyFocusProperties(focusProperties: FocusProperties) {
+            focusProperties.canFocus = canFocus
+        }
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CustomFocusTraversalTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CustomFocusTraversalTest.kt
index c3f7bcc..e3749d3 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CustomFocusTraversalTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CustomFocusTraversalTest.kt
@@ -22,6 +22,9 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.key.Key.Companion.Tab
@@ -42,6 +45,7 @@
 import org.junit.runner.RunWith
 import com.google.common.truth.Truth.assertThat
 import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
 
 @ExperimentalComposeUiApi
 @MediumTest
@@ -55,7 +59,7 @@
 
     companion object {
         @JvmStatic
-        @Parameterized.Parameters(name = "moveFocusProgrammatically = {0}, useFocusModifier = {1}")
+        @Parameters(name = "moveFocusProgrammatically = {0}, useFocusOrderModifier = {1}")
         fun initParameters() = listOf(
             arrayOf(true, true),
             arrayOf(true, false),
@@ -713,7 +717,7 @@
     }
 
     @Test
-    fun focusProperties_emptyfocusPropertiesInParent_doesNotResetCustomNextSetByChild() {
+    fun focusProperties_emptyFocusPropertiesInParent_doesNotResetCustomNextSetByChild() {
         // Arrange.
         var item1Focused = false
         var item2Focused = false
@@ -764,8 +768,128 @@
         }
     }
 
+    @Test
+    fun changedFocusProperties() {
+        // Arrange.
+        var item1Focused = false
+        var item2Focused = false
+        var item3Focused = false
+        var item4Focused = false
+        val (item1, item3, item4) = FocusRequester.createRefs()
+        var nextItem = item3
+        lateinit var focusManager: FocusManager
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            Row {
+                Box(
+                    Modifier
+                        .focusRequester(item1)
+                        .dynamicFocusProperties { next = nextItem }
+                        .onFocusChanged { item1Focused = it.isFocused }
+                        .focusTarget()
+                )
+                Box(
+                    Modifier
+                        .onFocusChanged { item2Focused = it.isFocused }
+                        .focusTarget()
+                )
+                Box(
+                    Modifier
+                        .focusRequester(item3)
+                        .onFocusChanged { item3Focused = it.isFocused }
+                        .focusTarget()
+                )
+                Box(
+                    Modifier
+                        .focusRequester(item4)
+                        .onFocusChanged { item4Focused = it.isFocused }
+                        .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle { item1.requestFocus() }
+
+        // Act.
+        rule.runOnIdle {
+            nextItem = item4
+        }
+        if (moveFocusProgrammatically) {
+            rule.runOnIdle {
+                focusManager.moveFocus(FocusDirection.Next)
+            }
+        } else {
+            rule.onRoot().performKeyPress(KeyEvent(AndroidKeyEvent(KeyDown, Tab.nativeKeyCode)))
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(item1Focused).isFalse()
+            assertThat(item2Focused).isFalse()
+            assertThat(item3Focused).isFalse()
+            assertThat(item4Focused).isTrue()
+        }
+    }
+
+    @Test
+    fun changedFocusProperties_mutableState() {
+        // Arrange.
+        var item1Focused = false
+        var item2Focused = false
+        var item3Focused = false
+        var item4Focused = false
+        val (item1, item3, item4) = FocusRequester.createRefs()
+        var nextItem by mutableStateOf(item3)
+        lateinit var focusManager: FocusManager
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            Row {
+                Box(
+                    Modifier
+                        .focusRequester(item1)
+                        .dynamicFocusProperties { next = nextItem }
+                        .onFocusChanged { item1Focused = it.isFocused }
+                        .focusTarget()
+                )
+                Box(
+                    Modifier
+                        .onFocusChanged { item2Focused = it.isFocused }
+                        .focusTarget()
+                )
+                Box(
+                    Modifier
+                        .focusRequester(item3)
+                        .onFocusChanged { item3Focused = it.isFocused }
+                        .focusTarget()
+                )
+                Box(
+                    Modifier
+                        .focusRequester(item4)
+                        .onFocusChanged { item4Focused = it.isFocused }
+                        .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle { item1.requestFocus() }
+
+        // Act.
+        rule.runOnIdle { nextItem = item4 }
+        if (moveFocusProgrammatically) {
+            rule.runOnIdle { focusManager.moveFocus(FocusDirection.Next) }
+        } else {
+            rule.onRoot().performKeyPress(KeyEvent(AndroidKeyEvent(KeyDown, Tab.nativeKeyCode)))
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(item1Focused).isFalse()
+            assertThat(item2Focused).isFalse()
+            assertThat(item3Focused).isFalse()
+            assertThat(item4Focused).isTrue()
+        }
+    }
+
     @Suppress("DEPRECATION")
-    fun Modifier.dynamicFocusProperties(block: FocusOrder.() -> Unit): Modifier =
+    private fun Modifier.dynamicFocusProperties(block: FocusOrder.() -> Unit): Modifier =
         if (useFocusOrderModifier) {
             this.then(ReceiverFocusOrderModifier(block))
         } else {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/DeactivatedFocusPropertiesTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/DeactivatedFocusPropertiesTest.kt
deleted file mode 100644
index b5a057b..0000000
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/DeactivatedFocusPropertiesTest.kt
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.focus
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class DeactivatedFocusPropertiesTest {
-    @get:Rule
-    val rule = createComposeRule()
-
-    @Test
-    fun notDeactivatedByDefault() {
-        // Arrange.
-        var isDeactivated: Boolean? = null
-        rule.setFocusableContent {
-            Box(modifier = Modifier
-                .onFocusChanged { isDeactivated = it.isDeactivated }
-                .focusTarget()
-            )
-        }
-
-        // Assert.
-        rule.runOnIdle { assertThat(isDeactivated).isFalse() }
-    }
-
-    @Test
-    fun initializedAsNotDeactivated() {
-        // Arrange.
-        var deactivated: Boolean? = null
-        rule.setFocusableContent {
-            Box(modifier = Modifier
-                .focusProperties { canFocus = true }
-                .onFocusChanged { deactivated = it.isDeactivated }
-                .focusTarget()
-            )
-        }
-
-        // Assert.
-        rule.runOnIdle { assertThat(deactivated).isFalse() }
-    }
-
-    @Test
-    fun initializedAsDeactivated() {
-        // Arrange.
-        var isDeactivated: Boolean? = null
-        rule.setFocusableContent {
-            Box(modifier = Modifier
-                .focusProperties { canFocus = false }
-                .onFocusChanged { isDeactivated = it.isDeactivated }
-                .focusTarget()
-            )
-        }
-
-        // Assert.
-        rule.runOnIdle { assertThat(isDeactivated).isTrue() }
-    }
-
-    @Test
-    fun leftMostDeactivatedPropertyTakesPrecedence() {
-        // Arrange.
-        var deactivated: Boolean? = null
-        rule.setFocusableContent {
-            Box(modifier = Modifier
-                .focusProperties { canFocus = false }
-                .focusProperties { canFocus = true }
-                .onFocusChanged { deactivated = it.isDeactivated }
-                .focusTarget()
-            )
-        }
-
-        // Assert.
-        rule.runOnIdle { assertThat(deactivated).isTrue() }
-    }
-
-    @Test
-    fun leftMostNonDeactivatedPropertyTakesPrecedence() {
-        // Arrange.
-        var deactivated: Boolean? = null
-        rule.setFocusableContent {
-            Box(modifier = Modifier
-                .focusProperties { canFocus = true }
-                .focusProperties { canFocus = false }
-                .onFocusChanged { deactivated = it.isDeactivated }
-                .focusTarget()
-            )
-        }
-
-        // Assert.
-        rule.runOnIdle { assertThat(deactivated).isFalse() }
-    }
-
-    @Test
-    fun ParentsDeactivatedPropertyTakesPrecedence() {
-        // Arrange.
-        var deactivated: Boolean? = null
-        rule.setFocusableContent {
-            Box(modifier = Modifier.focusProperties { canFocus = false }) {
-                Box(modifier = Modifier
-                    .focusProperties { canFocus = true }
-                    .onFocusChanged { deactivated = it.isDeactivated }
-                    .focusTarget()
-                )
-            }
-        }
-
-        // Assert.
-        rule.runOnIdle { assertThat(deactivated).isTrue() }
-    }
-
-    @Test
-    fun ParentsNotDeactivatedPropertyTakesPrecedence() {
-        // Arrange.
-        var deactivated: Boolean? = null
-        rule.setFocusableContent {
-            Box(modifier = Modifier.focusProperties { canFocus = true }) {
-                Box(modifier = Modifier
-                    .focusProperties { canFocus = false }
-                    .onFocusChanged { deactivated = it.isDeactivated }
-                    .focusTarget()
-                )
-            }
-        }
-
-        // Assert.
-        rule.runOnIdle { assertThat(deactivated).isFalse() }
-    }
-
-    @Test
-    fun deactivatedItemDoesNotGainFocus() {
-        // Arrange.
-        var isFocused: Boolean? = null
-        val focusRequester = FocusRequester()
-        rule.setFocusableContent {
-            Box(modifier = Modifier
-                .focusProperties { canFocus = false }
-                .focusRequester(focusRequester)
-                .onFocusChanged { isFocused = it.isFocused }
-                .focusTarget()
-            )
-        }
-
-        // Act.
-        rule.runOnIdle { focusRequester.requestFocus() }
-
-        // Assert.
-        rule.runOnIdle { assertThat(isFocused).isFalse() }
-    }
-
-    @Test
-    fun deactivatedFocusPropertiesOnNonFocusableParentAppliesToAllChildren() {
-        // Arrange.
-        var isParentDeactivated: Boolean? = null
-        var isChild1Deactivated: Boolean? = null
-        var isChild2Deactivated: Boolean? = null
-        var isGrandChildDeactivated: Boolean? = null
-        rule.setFocusableContent {
-            Column(modifier = Modifier
-                .focusProperties { canFocus = false }
-                .onFocusChanged { isParentDeactivated = it.isDeactivated }
-            ) {
-                Box(modifier = Modifier
-                    .onFocusChanged { isChild1Deactivated = it.isDeactivated }
-                    .focusTarget()
-                )
-                Box(modifier = Modifier
-                        .onFocusChanged { isChild2Deactivated = it.isDeactivated }
-                        .focusTarget()
-                ) {
-                    Box(modifier = Modifier
-                        .onFocusChanged { isGrandChildDeactivated = it.isDeactivated }
-                        .focusTarget()
-                    )
-                }
-            }
-        }
-
-        // Assert.
-        rule.runOnIdle { assertThat(isParentDeactivated).isTrue() }
-        rule.runOnIdle { assertThat(isChild1Deactivated).isTrue() }
-        rule.runOnIdle { assertThat(isChild2Deactivated).isTrue() }
-        rule.runOnIdle { assertThat(isGrandChildDeactivated).isFalse() }
-    }
-
-    @Test
-    fun focusedItemLosesFocusWhenDeactivated() {
-        // Arrange.
-        var isFocused: Boolean? = null
-        val focusRequester = FocusRequester()
-        var deactivated by mutableStateOf(false)
-        rule.setFocusableContent {
-            Box(modifier = Modifier
-                .focusProperties { canFocus = !deactivated }
-                .focusRequester(focusRequester)
-                .onFocusChanged { isFocused = it.isFocused }
-                .focusTarget()
-            )
-        }
-        rule.runOnIdle {
-            focusRequester.requestFocus()
-            assertThat(isFocused).isTrue()
-        }
-
-        // Act.
-        rule.runOnIdle { deactivated = true }
-
-        // Assert.
-        rule.runOnIdle { assertThat(isFocused).isFalse() }
-    }
-}
-
-private val FocusState.isDeactivated: Boolean
-    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindFocusableChildrenTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindFocusableChildrenTest.kt
deleted file mode 100644
index 1fc85e9..0000000
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindFocusableChildrenTest.kt
+++ /dev/null
@@ -1,193 +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.compose.ui.focus
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
-import androidx.compose.ui.graphics.Color.Companion.Red
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@MediumTest
-@RunWith(Parameterized::class)
-class FindFocusableChildrenTest(private val excludeDeactivated: Boolean) {
-    @get:Rule
-    val rule = createComposeRule()
-
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "excludeDeactivated = {0}")
-        fun initParameters() = listOf(true, false)
-    }
-
-    @Test
-    fun returnsFirstFocusNodeInModifierChain() {
-        val focusModifier1 = FocusModifier(Inactive)
-        val focusModifier2 = FocusModifier(Inactive)
-        val focusModifier3 = FocusModifier(Inactive)
-        val focusModifier4 = FocusModifier(Inactive)
-        // Arrange.
-        // layoutNode--focusNode1--focusNode2--focusNode3--focusNode4
-        rule.setContent {
-            Box(
-                Modifier
-                    .focusTarget(focusModifier1)
-                    .focusProperties { canFocus = false }
-                    .focusTarget(focusModifier2)
-                    .focusTarget(focusModifier3)
-                    .focusTarget(focusModifier4)
-            )
-        }
-
-        // Act.
-        val focusableChildren = rule.runOnIdle {
-            focusModifier1.focusableChildren(excludeDeactivated)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            if (excludeDeactivated) {
-                assertThat(focusableChildren).isExactly(focusModifier3)
-            } else {
-                assertThat(focusableChildren).isExactly(focusModifier2)
-            }
-        }
-    }
-
-    @Test
-    fun skipsNonFocusNodesAndReturnsFirstFocusNodeInModifierChain() {
-        val focusModifier1 = FocusModifier(Inactive)
-        val focusModifier2 = FocusModifier(Inactive)
-        val focusModifier3 = FocusModifier(Inactive)
-        // Arrange.
-        // layoutNode--focusNode1--nonFocusNode--focusNode2--focusNode3
-        rule.setContent {
-            Box(
-                Modifier
-                    .focusTarget(focusModifier1)
-                    .background(color = Red)
-                    .focusProperties { canFocus = false }
-                    .focusTarget(focusModifier2)
-                    .focusTarget(focusModifier3)
-            )
-        }
-
-        // Act.
-        val focusableChildren = rule.runOnIdle {
-            focusModifier1.focusableChildren(excludeDeactivated)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            if (excludeDeactivated) {
-                assertThat(focusableChildren).isExactly(focusModifier3)
-            } else {
-                assertThat(focusableChildren).isExactly(focusModifier2)
-            }
-        }
-    }
-
-    @Test
-    fun returnsFirstFocusChildOfEachChildLayoutNode() {
-        // Arrange.
-        // parentLayoutNode--parentFocusNode
-        //       |___________________________________________
-        //       |                                          |
-        // childLayoutNode1--focusNode1--focusNode2    childLayoutNode2--focusNode3--focusNode4
-        val parentFocusModifier = FocusModifier(Inactive)
-        val focusModifier1 = FocusModifier(Inactive)
-        val focusModifier2 = FocusModifier(Inactive)
-        val focusModifier3 = FocusModifier(Inactive)
-        val focusModifier4 = FocusModifier(Inactive)
-        rule.setContent {
-            Box(Modifier.focusTarget(parentFocusModifier)) {
-                Box(
-                    Modifier
-                        .focusProperties { canFocus = false }
-                        .focusTarget(focusModifier1)
-                        .focusTarget(focusModifier2)
-                )
-                Box(
-                    Modifier
-                        .focusTarget(focusModifier3)
-                        .focusProperties { canFocus = false }
-                        .focusTarget(focusModifier4)
-                )
-            }
-        }
-
-        // Act.
-        val focusableChildren = rule.runOnIdle {
-            parentFocusModifier.focusableChildren(excludeDeactivated)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            if (excludeDeactivated) {
-                assertThat(focusableChildren).isExactly(
-                    focusModifier2, focusModifier3
-                )
-            } else {
-                assertThat(focusableChildren).isExactly(
-                    focusModifier1, focusModifier3
-                )
-            }
-        }
-    }
-
-    @OptIn(ExperimentalComposeUiApi::class)
-    @Test
-    fun focusedChildIsAvailableFromOnFocusEvent() {
-        // Arrange.
-        val parentFocusModifier = FocusModifier(Inactive)
-        val childFocusModifier = FocusModifier(Inactive)
-        val focusRequester = FocusRequester()
-        var focusedChildAtTimeOfEvent: FocusModifier? = null
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(parentFocusModifier)) {
-                Box(
-                    Modifier
-                        .onFocusEvent {
-                            if (it.isFocused) {
-                                focusedChildAtTimeOfEvent = parentFocusModifier.focusedChild
-                            }
-                        }
-                        .focusRequester(focusRequester)
-                        .focusTarget(childFocusModifier)
-                )
-            }
-        }
-
-        // Act.
-        rule.runOnIdle { focusRequester.requestFocus() }
-
-        // Assert.
-        assertThat(focusedChildAtTimeOfEvent)
-            .isEqualTo(childFocusModifier)
-    }
-
-    private fun FocusModifier.focusableChildren(excludeDeactivated: Boolean): List<FocusModifier> =
-        (if (excludeDeactivated) activatedChildren() else children).asMutableList()
-}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindParentFocusNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindParentFocusNodeTest.kt
deleted file mode 100644
index abb72aa..0000000
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FindParentFocusNodeTest.kt
+++ /dev/null
@@ -1,227 +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.compose.ui.focus
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
-import androidx.compose.ui.graphics.Color.Companion.Red
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@MediumTest
-@RunWith(Parameterized::class)
-class FindParentFocusNodeTest(private val deactivated: Boolean) {
-    @get:Rule
-    val rule = createComposeRule()
-
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "isDeactivated = {0}")
-        fun initParameters() = listOf(true, false)
-    }
-
-    @Test
-    fun noParentReturnsNull() {
-        // Arrange.
-        val focusModifier = FocusModifier(Inactive)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(focusModifier))
-        }
-
-        // Act.
-        val rootFocusNode = rule.runOnIdle {
-            focusModifier.parent!!.parent
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(rootFocusNode).isNull()
-        }
-    }
-
-    @Test
-    fun returnsImmediateParentFromModifierChain() {
-        // Arrange.
-        // focusNode1--focusNode2--focusNode3--focusNode4--focusNode5
-        val modifier1 = FocusModifier(Inactive)
-        val modifier2 = FocusModifier(Inactive)
-        val modifier3 = FocusModifier(Inactive)
-        val modifier4 = FocusModifier(Inactive)
-        val modifier5 = FocusModifier(Inactive)
-        rule.setFocusableContent {
-            Box(
-                Modifier
-                    .focusTarget(modifier1)
-                    .focusProperties { canFocus = !deactivated }
-                    .focusTarget(modifier2)
-                    .focusTarget(modifier3)
-                    .focusTarget(modifier4)
-                    .focusTarget(modifier5)
-            )
-        }
-
-        // Act.
-        val parent = rule.runOnIdle {
-            modifier3.parent
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(parent).isEqualTo(modifier2)
-        }
-    }
-
-    @Test
-    fun returnsImmediateParentFromModifierChain_ignoresNonFocusModifiers() {
-        // Arrange.
-        // focusNode1--focusNode2--nonFocusNode--focusNode3
-        val modifier1 = FocusModifier(Inactive)
-        val modifier2 = FocusModifier(Inactive)
-        val modifier3 = FocusModifier(Inactive)
-        rule.setFocusableContent {
-            Box(
-                Modifier
-                    .focusTarget(modifier1)
-                    .focusProperties { canFocus = !deactivated }
-                    .focusTarget(modifier2)
-                    .background(color = Red)
-                    .focusTarget(modifier3)
-            )
-        }
-
-        // Act.
-        val parent = rule.runOnIdle {
-            modifier3.parent
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(parent).isEqualTo(modifier2)
-        }
-    }
-
-    @Test
-    fun returnsLastFocusParentFromParentLayoutNode() {
-        // Arrange.
-        // parentLayoutNode--parentFocusNode1--parentFocusNode2
-        //       |
-        // layoutNode--focusNode
-        val parentFocusModifier1 = FocusModifier(Inactive)
-        val parentFocusModifier2 = FocusModifier(Inactive)
-        val focusModifier = FocusModifier(Inactive)
-        rule.setFocusableContent {
-            Box(
-                Modifier
-                    .focusTarget(parentFocusModifier1)
-                    .focusProperties { canFocus = !deactivated }
-                    .focusTarget(parentFocusModifier2)
-            ) {
-                Box(Modifier.focusTarget(focusModifier))
-            }
-        }
-
-        // Act.
-        val parent = rule.runOnIdle {
-            focusModifier.parent
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(parent).isEqualTo(parentFocusModifier2)
-        }
-    }
-
-    @Test
-    fun returnsImmediateParent() {
-        // Arrange.
-        // greatGrandparentLayoutNode--greatGrandparentFocusNode
-        //       |
-        // grandparentLayoutNode--grandparentFocusNode
-        //       |
-        // parentLayoutNode--parentFocusNode
-        //       |
-        // layoutNode--focusNode
-        val greatGrandparentFocusModifier = FocusModifier(Inactive)
-        val grandparentFocusModifier = FocusModifier(Inactive)
-        val parentFocusModifier = FocusModifier(Inactive)
-        val focusModifier = FocusModifier(Inactive)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(greatGrandparentFocusModifier)) {
-                Box(Modifier.focusTarget(grandparentFocusModifier)) {
-                    Box(Modifier
-                        .focusProperties { canFocus = !deactivated }
-                        .focusTarget(parentFocusModifier)
-                    ) {
-                        Box(Modifier.focusTarget(focusModifier))
-                    }
-                }
-            }
-        }
-
-        // Act.
-        val parent = rule.runOnIdle {
-            focusModifier.parent
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(parent).isEqualTo(parentFocusModifier)
-        }
-    }
-
-    @Test
-    fun ignoresIntermediateLayoutNodesThatDoNotHaveFocusNodes() {
-        // Arrange.
-        // grandparentLayoutNode--grandparentFocusNode
-        //       |
-        // parentLayoutNode
-        //       |
-        // layoutNode--focusNode
-        val greatGrandparentFocusModifier = FocusModifier(Inactive)
-        val grandparentFocusModifier = FocusModifier(Inactive)
-        val focusModifier = FocusModifier(Inactive)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(greatGrandparentFocusModifier)) {
-                Box(Modifier
-                    .focusProperties { canFocus = !deactivated }
-                    .focusTarget(grandparentFocusModifier)
-                ) {
-                    Box {
-                        Box(Modifier.focusTarget(focusModifier))
-                    }
-                }
-            }
-        }
-
-        // Act.
-        val parent = rule.runOnIdle {
-            focusModifier.parent
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(parent).isEqualTo(grandparentFocusModifier)
-        }
-    }
-}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusAggregationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusAggregationTest.kt
index 4adc747..bff41d8 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusAggregationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusAggregationTest.kt
@@ -44,9 +44,11 @@
         }
 
         // Assert.
-        assertThat(focusState.isFocused).isFalse()
-        assertThat(focusState.hasFocus).isFalse()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isFalse()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -64,9 +66,11 @@
         }
 
         // Assert.
-        assertThat(focusState.isFocused).isFalse()
-        assertThat(focusState.hasFocus).isFalse()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isFalse()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -87,9 +91,11 @@
         rule.runOnIdle { focusRequester.requestFocus() }
 
         // Assert.
-        assertThat(focusState.isFocused).isTrue()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -103,9 +109,11 @@
         }
 
         // Assert.
-        assertThat(focusState.isFocused).isFalse()
-        assertThat(focusState.hasFocus).isFalse()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isFalse()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -119,9 +127,11 @@
         }
 
         // Assert.
-        assertThat(focusState.isFocused).isFalse()
-        assertThat(focusState.hasFocus).isFalse()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isFalse()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -139,9 +149,11 @@
         rule.runOnIdle { focusRequester.requestFocus() }
 
         // Assert.
-        assertThat(focusState.isFocused).isTrue()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -160,9 +172,11 @@
         rule.runOnIdle { focusRequester.requestFocus() }
 
         // Assert.
-        assertThat(focusState.isFocused).isTrue()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -181,9 +195,11 @@
         rule.runOnIdle { focusRequester.requestFocus() }
 
         // Assert.
-        assertThat(focusState.isFocused).isTrue()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -205,9 +221,11 @@
         }
 
         // Assert.
-        assertThat(focusState.isFocused).isTrue()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isTrue()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isTrue()
+        }
     }
 
     @Test
@@ -229,9 +247,11 @@
         }
 
         // Assert.
-        assertThat(focusState.isFocused).isTrue()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isTrue()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isTrue()
+        }
     }
 
     @Test
@@ -250,9 +270,11 @@
         rule.runOnIdle { focusRequester.requestFocus() }
 
         // Assert.
-        assertThat(focusState.isFocused).isFalse()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -272,9 +294,11 @@
         rule.runOnIdle { focusRequester.requestFocus() }
 
         // Assert.
-        assertThat(focusState.isFocused).isFalse()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -295,9 +319,11 @@
         rule.runOnIdle { focusRequester.requestFocus() }
 
         // Assert.
-        assertThat(focusState.isFocused).isFalse()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 
     @Test
@@ -322,8 +348,10 @@
         rule.runOnIdle { focusRequester.requestFocus() }
 
         // Assert.
-        assertThat(focusState.isFocused).isFalse()
-        assertThat(focusState.hasFocus).isTrue()
-        assertThat(focusState.isCaptured).isFalse()
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isCaptured).isFalse()
+        }
     }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt
index 88b4d3c..7b954bd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusChangedTest.kt
@@ -19,9 +19,6 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusStateImpl.Active
-import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -46,17 +43,16 @@
                 modifier = Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Active))
+                    .focusTarget()
             )
         }
+        rule.runOnIdle { focusRequester.requestFocus() }
 
-        rule.runOnIdle {
-            // Act.
-            focusRequester.requestFocus()
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
 
-            // Assert.
-            assertThat(focusState.isFocused).isTrue()
-        }
+        // Assert.
+        rule.runOnIdle { assertThat(focusState.isFocused).isTrue() }
     }
 
     @ExperimentalComposeUiApi
@@ -86,11 +82,11 @@
             assertThat(focusState.hasFocus).isTrue()
         }
 
-        rule.runOnIdle {
-            // Act.
-            focusRequester.requestFocus()
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
 
-            // Assert.
+        // Assert.
+        rule.runOnIdle {
             assertThat(focusState.isFocused).isTrue()
             assertThat(childFocusState.isFocused).isFalse()
         }
@@ -106,17 +102,20 @@
                 modifier = Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Captured))
+                    .focusTarget()
             )
         }
-
         rule.runOnIdle {
-            // Act.
             focusRequester.requestFocus()
-
-            // Assert.
+            focusRequester.captureFocus()
             assertThat(focusState.isCaptured).isTrue()
         }
+
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusState.isCaptured).isTrue() }
     }
 
     @Test
@@ -134,13 +133,11 @@
             )
         }
 
-        rule.runOnIdle {
-            // Act.
-            focusRequester.requestFocus()
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
 
-            // Assert.
-            assertThat(focusState.isDeactivated).isTrue()
-        }
+        // Assert.
+        rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
     }
 
     @ExperimentalComposeUiApi
@@ -171,18 +168,16 @@
             assertThat(childFocusState.isFocused).isTrue()
             assertThat(focusState.hasFocus).isTrue()
             assertThat(focusState.isFocused).isFalse()
-            assertThat(focusState.isDeactivated).isTrue()
         }
 
-        rule.runOnIdle {
-            // Act.
-            focusRequester.requestFocus()
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
 
-            // Assert.
+        // Assert.
+        rule.runOnIdle {
             assertThat(childFocusState.isFocused).isTrue()
             assertThat(focusState.hasFocus).isTrue()
             assertThat(focusState.isFocused).isFalse()
-            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 
@@ -196,17 +191,15 @@
                 modifier = Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
         }
 
-        rule.runOnIdle {
-            // Act.
-            focusRequester.requestFocus()
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
 
-            // Assert.
-            assertThat(focusState.isFocused).isTrue()
-        }
+        // Assert.
+        rule.runOnIdle { assertThat(focusState.isFocused).isTrue() }
     }
 
     @Test
@@ -236,18 +229,18 @@
                                 .onFocusChanged { focusState5 = it }
                                 .onFocusChanged { focusState6 = it }
                                 .focusRequester(focusRequester)
-                                .focusTarget(FocusModifier(Inactive))
+                                .focusTarget()
                         )
                     }
                 }
             }
         }
 
-        rule.runOnIdle {
-            // Act.
-            focusRequester.requestFocus()
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
 
-            // Assert.
+        // Assert.
+        rule.runOnIdle {
             assertThat(focusState1.isFocused).isTrue()
             assertThat(focusState2.isFocused).isTrue()
             assertThat(focusState3.isFocused).isTrue()
@@ -278,11 +271,11 @@
             )
         }
 
-        rule.runOnIdle {
-            // Act.
-            focusRequester.requestFocus()
+        // Act.
+        rule.runOnIdle { focusRequester.requestFocus() }
 
-            // Assert.
+        // Assert.
+        rule.runOnIdle {
             assertThat(focusState1.hasFocus).isTrue()
             assertThat(focusState2.hasFocus).isTrue()
             assertThat(focusState3.isFocused).isTrue()
@@ -290,6 +283,3 @@
         }
     }
 }
-
-private val FocusState.isDeactivated: Boolean
-    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
index 71af6e1..7c6cc56 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusEventCountTest.kt
@@ -20,9 +20,9 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Active
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -52,14 +52,15 @@
         val FocusEventModifierCall: Modifier.((FocusState) -> Unit) -> Modifier = {
             focusEventModifier(it)
         }
-        const val UseOnFocusEvent = "onFocusEvent"
-        const val UseFocusEventModifier = "FocusEventModifier"
+        private const val UseOnFocusEvent = "onFocusEvent"
+        private const val UseFocusEventModifier = "FocusEventModifier"
 
         @JvmStatic
         @Parameterized.Parameters(name = ">
         fun initParameters() = listOf(UseOnFocusEvent, UseFocusEventModifier)
 
         private fun Modifier.focusEventModifier(event: (FocusState) -> Unit) = this.then(
+            @Suppress("DEPRECATION")
             object : FocusEventModifier {
                 override fun onFocusEvent(focusState: FocusState) = event(focusState)
             }
@@ -81,11 +82,7 @@
         }
 
         // Assert.
-        rule.runOnIdle {
-            assertThat(focusStates).isExactly(
-                Inactive, // triggered by onFocusEvent node's onModifierChanged().
-            )
-        }
+        rule.runOnIdle { assertThat(focusStates).isExactly(Inactive) }
     }
 
     @Test
@@ -199,8 +196,8 @@
         // Assert.
         rule.runOnIdle {
             assertThat(focusStates).isExactly(
-                Inactive, // triggered by focus node's state change.
-                Inactive, // triggered by onFocusEvent node's onModifierChanged().
+                Inactive, // triggered by clearFocus() of the active node.
+                Inactive, // triggered by onFocusEvent node's attach().
             )
         }
     }
@@ -227,7 +224,63 @@
     }
 
     @Test
-    fun addingFocusTarget_onFocusEventIsCalledTwice() {
+    fun removingInactiveFocusNode_withActiveChild_onFocusEventIsCalledOnce() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        val focusRequester = FocusRequester()
+        var addFocusTarget by mutableStateOf(true)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .then(if (addFocusTarget) Modifier.focusTarget() else Modifier)
+                    .focusRequester(focusRequester)
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            focusStates.clear()
+        }
+
+        // Act.
+        rule.runOnIdle { addFocusTarget = false }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isExactly(Active) }
+    }
+
+    @Test
+    fun removingInactiveFocusNode_withActiveChildLayout_onFocusEventIsCalledOnce() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        val focusRequester = FocusRequester()
+        var addFocusTarget by mutableStateOf(true)
+        rule.setFocusableContent {
+            Box(Modifier.onFocusEvent { focusStates.add(it) }) {
+                Box(if (addFocusTarget) Modifier.focusTarget() else Modifier) {
+                    Box(
+                        modifier = Modifier
+                            .focusRequester(focusRequester)
+                            .focusTarget()
+                    )
+                }
+            }
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            focusStates.clear()
+        }
+
+        // Act.
+        rule.runOnIdle { addFocusTarget = false }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isExactly(Active) }
+    }
+
+    @Test
+    fun addingFocusTarget_onFocusEventIsCalledOnce() {
         // Arrange.
         val focusStates = mutableListOf<FocusState>()
         var addFocusTarget by mutableStateOf(false)
@@ -244,15 +297,11 @@
         rule.runOnIdle { addFocusTarget = true }
 
         // Assert.
-        rule.runOnIdle {
-            assertThat(focusStates).isExactly(
-                Inactive, // triggered by focus node's SideEffect.
-            )
-        }
+        rule.runOnIdle { assertThat(focusStates).isExactly(Inactive) }
     }
 
     @Test
-    fun addingEmptyFocusProperties_onFocusEventIsCalledTwice() {
+    fun addingEmptyFocusProperties_onFocusEventIsTriggered() {
         // Arrange.
         val focusStates = mutableListOf<FocusState>()
         var addFocusProperties by mutableStateOf(false)
@@ -270,54 +319,168 @@
         rule.runOnIdle { addFocusProperties = true }
 
         // Assert.
-        rule.runOnIdle {
-            assertThat(focusStates).isExactly(
-                Inactive, // triggered by focus node's property change.
-            )
-        }
+        rule.runOnIdle { assertThat(focusStates).isExactly(Inactive) }
     }
 
     @Test
-    fun deactivatingFocusNode_onFocusEventIsCalledOnce() {
+    fun addingCanFocusProperty_onFocusEventIsTriggered() {
         // Arrange.
         val focusStates = mutableListOf<FocusState>()
-        var deactiated by mutableStateOf(false)
+        var addFocusProperties by mutableStateOf(false)
         rule.setFocusableContent {
             Box(
                 modifier = Modifier
                     .onFocusEvent { focusStates.add(it) }
-                    .focusProperties { canFocus = !deactiated }
+                    .then(
+                        if (addFocusProperties) {
+                            Modifier.focusProperties { canFocus = true }
+                        } else {
+                            Modifier
+                        }
+                    )
                     .focusTarget()
             )
         }
         rule.runOnIdle { focusStates.clear() }
 
         // Act.
-        rule.runOnIdle { deactiated = true }
-
-        // Assert.
-        rule.runOnIdle { assertThat(focusStates).isExactly(Deactivated) }
-    }
-
-    @Test
-    fun activatingFocusNode_onFocusEventIsCalledOnce() {
-        // Arrange.
-        val focusStates = mutableListOf<FocusState>()
-        var deactiated by mutableStateOf(true)
-        rule.setFocusableContent {
-            Box(
-                modifier = Modifier
-                    .onFocusEvent { focusStates.add(it) }
-                    .focusProperties { canFocus = !deactiated }
-                    .focusTarget()
-            )
-        }
-        rule.runOnIdle { focusStates.clear() }
-
-        // Act.
-        rule.runOnIdle { deactiated = false }
+        rule.runOnIdle { addFocusProperties = true }
 
         // Assert.
         rule.runOnIdle { assertThat(focusStates).isExactly(Inactive) }
     }
+
+    @Test
+    fun addingCantFocusProperty_noFocusEventIsTriggered() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        var add by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .then(if (add) Modifier.focusProperties { canFocus = false } else Modifier)
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { add = true }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isExactly(Inactive) }
+    }
+
+    @Test
+    fun removingCanFocusProperty_onFocusEventIsTriggered() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        var remove by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .then(if (remove) Modifier else Modifier.focusProperties { canFocus = true })
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { remove = true }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isExactly(Inactive) }
+    }
+
+    @Test
+    fun removingCantFocusProperty_onFocusEventIsTriggered() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        var remove by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .then(if (remove) Modifier else Modifier.focusProperties { canFocus = false })
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { remove = true }
+
+        // Assert.
+         rule.runOnIdle { assertThat(focusStates).isExactly(Inactive) }
+    }
+
+    @Test
+    fun deactivatingFocusNode_noFocusEventIsCalledOnce() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        var deactivated by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .focusProperties { canFocus = !deactivated }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { deactivated = true }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isEmpty() }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun changingFocusProperty_onFocusEventIsNotCalled() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        val (item1, item2) = FocusRequester.createRefs()
+        var nextItem by mutableStateOf(item1)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .focusProperties { next = nextItem }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { nextItem = item2 }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isEmpty() }
+    }
+
+    @Test
+    fun activatingFocusNode_doesNotTriggerFocusEvent() {
+        // Arrange.
+        val focusStates = mutableListOf<FocusState>()
+        var canFocus by mutableStateOf(false)
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .onFocusEvent { focusStates.add(it) }
+                    .focusProperties { this.canFocus = canFocus }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusStates.clear() }
+
+        // Act.
+        rule.runOnIdle { canFocus = true }
+
+        // Assert.
+        rule.runOnIdle { assertThat(focusStates).isEmpty() }
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt
index 6f60d99..a462e04 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt
@@ -16,14 +16,20 @@
 
 package androidx.compose.ui.focus
 
+import android.view.View
 import androidx.compose.foundation.layout.Box
-import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusStateImpl.Active
+import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
+import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,11 +44,10 @@
     fun clearFocus_singleLayout() {
         // Arrange.
         lateinit var focusManager: FocusManager
-        lateinit var focusRequester: FocusRequester
         lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
         rule.setFocusableContent {
             focusManager = LocalFocusManager.current
-            focusRequester = remember { FocusRequester() }
             Box(
                 modifier = Modifier
                     .focusRequester(focusRequester)
@@ -59,20 +64,22 @@
         rule.runOnIdle { focusManager.clearFocus() }
 
         // Assert.
-        rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState.isFocused).isTrue()
+            assertThat(focusState.isFocused).isFalse()
+        }
     }
 
     @Test
     fun clearFocus_entireHierarchyIsCleared() {
         // Arrange.
         lateinit var focusManager: FocusManager
-        lateinit var focusRequester: FocusRequester
         lateinit var focusState: FocusState
         lateinit var parentFocusState: FocusState
         lateinit var grandparentFocusState: FocusState
+        val focusRequester = FocusRequester()
         rule.setFocusableContent {
             focusManager = LocalFocusManager.current
-            focusRequester = remember { FocusRequester() }
             Box(
                 modifier = Modifier
                     .onFocusChanged { grandparentFocusState = it }
@@ -109,4 +116,321 @@
             assertThat(focusState.isFocused).isFalse()
         }
     }
+
+    @Test
+    fun takeFocus_whenRootIsInactive() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        lateinit var view: View
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            view = LocalView.current
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { view.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState).isEqualTo(Active)
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    fun takeFocus_whenRootIsActive() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        lateinit var view: View
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            view = LocalView.current
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusManager.setRootFocusState(Active) }
+
+        // Act.
+        rule.runOnIdle { view.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState).isEqualTo(Active)
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    @Test
+    fun takeFocus_whenRootIsActiveParent() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        lateinit var view: View
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            view = LocalView.current
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Act.
+        rule.runOnIdle { view.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState).isEqualTo(ActiveParent)
+            assertThat(focusState.isFocused).isTrue()
+        }
+    }
+
+    @Test
+    fun releaseFocus_whenRootIsInactive() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        lateinit var view: View
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            view = LocalView.current
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { view.clearFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    fun releaseFocus_whenRootIsActive() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        lateinit var view: View
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            view = LocalView.current
+            Box(
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusManager.setRootFocusState(Active) }
+
+        // Act.
+        rule.runOnIdle { view.clearFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    @Ignore("b/257499180")
+    @Test
+    fun releaseFocus_whenRootIsActiveParent() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        lateinit var view: View
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            view = LocalView.current
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Act.
+        rule.runOnIdle {
+            view.clearFocus()
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    @Test
+    fun clearFocus_whenRootIsInactive() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { focusManager.clearFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    @Ignore("b/257499180")
+    @Test
+    fun clearFocus_whenRootIsActive() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusManager.setRootFocusState(Active) }
+
+        // Act.
+        rule.runOnIdle { focusManager.clearFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    @Test
+    fun clearFocus_whenRootIsActiveParent() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Act.
+        rule.runOnIdle { focusManager.clearFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            // TODO(b/257499180): Compose should not hold focus state when clear focus is requested.
+            assertThat(focusManager.rootFocusState).isEqualTo(Active)
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    @Test
+    fun clearFocus_whenHierarchyHasCapturedFocus() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            focusRequester.captureFocus()
+        }
+
+        // Act.
+        rule.runOnIdle { focusManager.clearFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusManager.rootFocusState).isEqualTo(ActiveParent)
+            assertThat(focusState.isFocused).isTrue()
+        }
+    }
+
+    @Test
+    fun clearFocus_forced_whenHierarchyHasCapturedFocus() {
+        // Arrange.
+        lateinit var focusManager: FocusManager
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            focusManager = LocalFocusManager.current
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            focusRequester.captureFocus()
+        }
+
+        // Act.
+        rule.runOnIdle { focusManager.clearFocus(force = true) }
+
+        // Assert.
+        rule.runOnIdle {
+            // TODO(b/257499180): Compose should clear focus and send focus to the root view.
+            assertThat(focusManager.rootFocusState).isEqualTo(Active)
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    private val FocusManager.rootFocusState: FocusState
+        get() = (this as FocusOwnerImpl).rootFocusNode.focusState
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    private fun FocusManager.setRootFocusState(focusState: FocusStateImpl) {
+        (this as FocusOwnerImpl).rootFocusNode.focusStateImpl = focusState
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt
index 581f8fe7..e9f9614 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt
@@ -19,25 +19,30 @@
 import android.view.View
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
 import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.compose.ui.focus.focusRequester as modifierNodeFocusRequester
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
 @MediumTest
-@RunWith(AndroidJUnit4::class)
-class FocusRequesterTest {
+@RunWith(Parameterized::class)
+class FocusRequesterTest(private val modifierNodeVersion: Boolean) {
     @get:Rule
     val rule = createComposeRule()
 
@@ -284,12 +289,12 @@
         }
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun requestFocus_onDeactivatedParent_focusesOnChild() {
         // Arrange.
         lateinit var childFocusState: FocusState
-        val (focusRequester, initialFocus) = FocusRequester.createRefs()
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
         rule.setFocusableContent {
             Column(
                 modifier = Modifier
@@ -363,6 +368,165 @@
         }
     }
 
+    @Test
+    fun requestFocus_DisabledParent_doesNotPerformImplicitEnterIfCanFocusIsFalse() {
+        // Arrange.
+        lateinit var childFocusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .focusProperties {
+                        canFocus = false
+                        @OptIn(ExperimentalComposeUiApi::class)
+                        enter = { FocusRequester.Cancel }
+                    }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget()
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            // Act.
+            focusRequester.requestFocus()
+
+            // Assert.
+            assertThat(childFocusState.isFocused).isFalse()
+        }
+    }
+
+    @Test
+    fun requestFocus_DisabledParents_selectsSiblingFromPathWhereCanFocusIsNotFalse() {
+        // Arrange.
+        lateinit var childFocusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget()
+                )
+                Box(
+                    modifier = Modifier
+                        .focusProperties {
+                            canFocus = false
+                            @OptIn(ExperimentalComposeUiApi::class)
+                            enter = { FocusRequester.Cancel }
+                        }
+                        .focusTarget()
+                ) {
+                    Box(modifier = Modifier.focusTarget())
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            // Act.
+            focusRequester.requestFocus()
+
+            // Assert.
+            assertThat(childFocusState.isFocused).isTrue()
+        }
+    }
+
+    @Test
+    fun requestFocus_intermediateDisabledParents_focusesOnLeftMostChild() {
+        // Arrange.
+        lateinit var childFocusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Row(
+                modifier = Modifier
+                    .size(100.dp)
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget()
+                )
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .focusProperties { canFocus = false }
+                        .focusTarget()
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(10.dp)
+                            .focusTarget()
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            // Act.
+            focusRequester.requestFocus()
+
+            // Assert.
+            assertThat(childFocusState.isFocused).isTrue()
+        }
+    }
+
+    @Test
+    fun requestFocus_intermediateDisabledParents_focusesOnLeftMostChild_regardlessOfDepth() {
+        // Arrange.
+        lateinit var childFocusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Row(
+                modifier = Modifier
+                    .size(100.dp)
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .focusProperties { canFocus = false }
+                        .focusTarget()
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(10.dp)
+                            .onFocusChanged { childFocusState = it }
+                            .focusTarget()
+                    )
+                }
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .focusTarget()
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            // Act.
+            focusRequester.requestFocus()
+
+            // Assert.
+            assertThat(childFocusState.isFocused).isTrue()
+        }
+    }
+
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun requestFocus_onDeactivatedNode_performsFocusEnter() {
@@ -531,4 +695,28 @@
             assertThat(focusState.isFocused).isTrue()
         }
     }
+
+    private fun Modifier.focusRequester(focusRequester: FocusRequester): Modifier {
+        return if (modifierNodeVersion) {
+            this.modifierNodeFocusRequester(focusRequester)
+        } else {
+            composed(debugInspectorInfo {
+                name = "focusRequester"
+                properties["focusRequester"] = focusRequester
+            }) {
+                remember(focusRequester) {
+                    object : @Suppress("DEPRECATION") FocusRequesterModifier {
+                        override val focusRequester: FocusRequester
+                            get() = focusRequester
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "modifierNodeVersion = {0}")
+        fun initParameters() = listOf(true, false)
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt
index 630f5cc..10b7e0e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt
@@ -118,7 +118,8 @@
         var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
             Box(
-                modifier = Modifier.onFocusChanged { focusState = it }
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
                     .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
             )
@@ -143,7 +144,8 @@
         var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
             Box(
-                modifier = Modifier.onFocusChanged { focusState = it }
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
                     .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
             ) {
@@ -170,7 +172,8 @@
         var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
             Box(
-                modifier = Modifier.onFocusChanged { focusState = it }
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
                     .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
             ) {
@@ -202,22 +205,27 @@
                     .onFocusChanged { focusState = it }
                     .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
             ) {
-                Box(modifier = Modifier
-                    .focusRequester(focusRequester)
-                    .focusTarget()
+                Box(
+                    modifier = Modifier
+                        .focusRequester(focusRequester)
+                        .focusTarget()
                 )
             }
         }
         rule.runOnIdle {
             focusRequester.requestFocus()
             assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isFocused).isFalse()
         }
 
         // Act.
         rule.runOnIdle { optionalFocusTarget = false }
 
         // Assert.
-        rule.runOnIdle { assertThat(focusState.isFocused).isTrue() }
+        rule.runOnIdle {
+            assertThat(focusState.hasFocus).isTrue()
+            assertThat(focusState.isFocused).isTrue()
+        }
     }
 
     @Test
@@ -228,7 +236,8 @@
         var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
             Box(
-                modifier = Modifier.onFocusChanged { focusState = it }
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
                     .then(
                         if (optionalFocusTarget) {
                             Modifier
@@ -267,15 +276,18 @@
                     .focusTarget()
             ) {
                 Box(
-                    modifier = Modifier.onFocusChanged { focusState = it }.then(
-                        if (optionalFocusTargets) {
-                            Modifier.focusTarget()
-                                .focusRequester(focusRequester)
-                                .focusTarget()
-                        } else {
-                            Modifier
-                        }
-                    )
+                    modifier = Modifier
+                        .onFocusChanged { focusState = it }
+                        .then(
+                            if (optionalFocusTargets) {
+                                Modifier
+                                    .focusTarget()
+                                    .focusRequester(focusRequester)
+                                    .focusTarget()
+                            } else {
+                                Modifier
+                            }
+                        )
                 )
             }
         }
@@ -314,16 +326,17 @@
                             Modifier
                     )
             ) {
-                Box(modifier = Modifier
-                    .focusRequester(focusRequester)
-                    .focusTarget()
+                Box(
+                    modifier = Modifier
+                        .focusRequester(focusRequester)
+                        .focusTarget()
                 )
             }
         }
         rule.runOnIdle {
             focusRequester.requestFocus()
+            assertThat(focusState.isFocused).isFalse()
             assertThat(focusState.hasFocus).isTrue()
-            assertThat(focusState.isDeactivated).isTrue()
         }
 
         // Act.
@@ -332,7 +345,6 @@
         // Assert.
         rule.runOnIdle {
             assertThat(focusState.isFocused).isTrue()
-            assertThat(focusState.isDeactivated).isFalse()
         }
     }
 
@@ -372,7 +384,6 @@
         rule.runOnIdle {
             focusRequester.requestFocus()
             assertThat(focusState.hasFocus).isTrue()
-            assertThat(focusState.isDeactivated).isTrue()
         }
 
         // Act.
@@ -382,7 +393,6 @@
         rule.runOnIdle {
             assertThat(focusState.isFocused).isFalse()
             assertThat(focusState.hasFocus).isTrue()
-            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 
@@ -401,13 +411,13 @@
             ) {
                 Box(
                     modifier = Modifier.then(
-                            if (optionalFocusTarget)
-                                Modifier
-                                    .focusProperties { canFocus = false }
-                                    .focusTarget()
-                            else
-                                Modifier
-                        )
+                        if (optionalFocusTarget)
+                            Modifier
+                                .focusProperties { canFocus = false }
+                                .focusTarget()
+                        else
+                            Modifier
+                    )
                 ) {
                     Box(
                         modifier = Modifier
@@ -421,7 +431,6 @@
             focusRequester.requestFocus()
             assertThat(focusState.isFocused).isFalse()
             assertThat(focusState.hasFocus).isTrue()
-            assertThat(focusState.isDeactivated).isTrue()
         }
 
         // Act.
@@ -431,7 +440,6 @@
         rule.runOnIdle {
             assertThat(focusState.isFocused).isFalse()
             assertThat(focusState.hasFocus).isTrue()
-            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 
@@ -475,7 +483,6 @@
         rule.runOnIdle {
             focusRequester.requestFocus()
             assertThat(focusState.hasFocus).isTrue()
-            assertThat(focusState.isDeactivated).isTrue()
         }
 
         // Act.
@@ -485,12 +492,11 @@
         rule.runOnIdle {
             assertThat(focusState.isFocused).isFalse()
             assertThat(focusState.hasFocus).isFalse()
-            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 
     @Test
-    fun removedNonDeactivatedParentAndActiveChild_grandParent_retainsNonDeactivatedState() {
+    fun removedDeactivatedParentAndActiveChild_grandParent_retainsNonDeactivatedState() {
         // Arrange.
         lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
@@ -503,13 +509,13 @@
             ) {
                 Box(
                     modifier = Modifier.then(
-                            if (optionalFocusTarget)
-                                Modifier
-                                    .focusProperties { canFocus = false }
-                                    .focusTarget()
-                            else
-                                Modifier
-                        )
+                        if (optionalFocusTarget)
+                            Modifier
+                                .focusProperties { canFocus = false }
+                                .focusTarget()
+                        else
+                            Modifier
+                    )
                 ) {
                     Box(
                         modifier = Modifier
@@ -527,7 +533,6 @@
         rule.runOnIdle {
             focusRequester.requestFocus()
             assertThat(focusState.hasFocus).isTrue()
-            assertThat(focusState.isDeactivated).isFalse()
         }
 
         // Act.
@@ -537,7 +542,6 @@
         rule.runOnIdle {
             assertThat(focusState.isFocused).isFalse()
             assertThat(focusState.hasFocus).isFalse()
-            assertThat(focusState.isDeactivated).isFalse()
         }
     }
 
@@ -549,18 +553,20 @@
         var optionalFocusTarget by mutableStateOf(true)
         rule.setFocusableContent {
             Box(
-                modifier = Modifier.onFocusChanged { focusState = it }
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
                     .then(if (optionalFocusTarget) Modifier.focusTarget() else Modifier)
                     .focusRequester(focusRequester)
                     .focusTarget()
             )
         }
+        rule.runOnIdle { focusRequester.requestFocus() }
 
         // Act.
         rule.runOnIdle { optionalFocusTarget = false }
 
         // Assert.
-        rule.runOnIdle { assertThat(focusState.isFocused).isFalse() }
+        rule.runOnIdle { assertThat(focusState.isFocused).isTrue() }
     }
 
     @Test
@@ -571,7 +577,8 @@
         var addFocusTarget by mutableStateOf(false)
         rule.setFocusableContent {
             Box(
-                modifier = Modifier.onFocusChanged { focusState = it }
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
                     .then(if (addFocusTarget) Modifier.focusTarget() else Modifier)
             ) {
@@ -598,7 +605,8 @@
         var addFocusTarget by mutableStateOf(false)
         rule.setFocusableContent {
             Box(
-                modifier = Modifier.onFocusChanged { focusState = it }
+                modifier = Modifier
+                    .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
                     .then(if (addFocusTarget) Modifier.focusTarget() else Modifier)
             )
@@ -641,11 +649,9 @@
         // Assert.
         rule.runOnIdle {
             assertThat(focusState.isFocused).isFalse()
-            assertThat(focusState.isDeactivated).isFalse()
         }
     }
 
-    @Test
     fun removingDeactivatedItem_withInactiveNextFocusTarget() {
         // Arrange.
         lateinit var focusState: FocusState
@@ -673,7 +679,6 @@
         // Assert.
         rule.runOnIdle {
             assertThat(focusState.isFocused).isFalse()
-            assertThat(focusState.isDeactivated).isFalse()
         }
     }
 
@@ -708,10 +713,6 @@
         // Assert.
         rule.runOnIdle {
             assertThat(focusState.isFocused).isFalse()
-            assertThat(focusState.isDeactivated).isTrue()
         }
     }
 }
-
-private val FocusState.isDeactivated: Boolean
-    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt
index cfb4a81..20fdc7a 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt
@@ -21,10 +21,8 @@
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.MeasurePolicy
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
@@ -39,7 +37,7 @@
  */
 internal fun ComposeContentTestRule.setFocusableContent(content: @Composable () -> Unit) {
     setContent {
-        Box(modifier = Modifier.requiredSize(10.dp, 10.dp)) { content() }
+        Box(modifier = Modifier.requiredSize(100.dp, 100.dp)) { content() }
     }
 }
 
@@ -91,13 +89,3 @@
 fun IterableSubject.isExactly(vararg expected: Any?) {
     return containsExactlyElementsIn(expected).inOrder()
 }
-
-/**
- * focusTarget needs a SideEffect to work.
- */
-internal fun Modifier.focusTarget(focusModifier: FocusModifier) = composed {
-    SideEffect {
-        focusModifier.sendOnFocusEvent()
-    }
-    this.then(focusModifier).then(ResetFocusModifierLocals)
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FreeFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FreeFocusTest.kt
index 0ecc2d73a..30cf72a 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FreeFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FreeFocusTest.kt
@@ -18,10 +18,6 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusStateImpl.Active
-import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
-import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -46,15 +42,16 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Active))
+                    .focusTarget()
             )
         }
+        rule.runOnIdle { focusRequester.requestFocus() }
 
+        // Act.
+        val success = rule.runOnIdle { focusRequester.freeFocus() }
+
+        // Assert.
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
-
-            // Assert.
             assertThat(success).isTrue()
             assertThat(focusState.isFocused).isTrue()
         }
@@ -64,21 +61,28 @@
     fun activeParent_freeFocus_retainFocusAsActiveParent() {
         // Arrange.
         lateinit var focusState: FocusState
+        val initialFocus = FocusRequester()
         val focusRequester = FocusRequester()
         rule.setFocusableContent {
             Box(
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(ActiveParent))
-            )
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .focusRequester(initialFocus)
+                        .focusTarget())
+            }
         }
+        rule.runOnIdle { initialFocus.requestFocus() }
 
+        // Act.
+        val success = rule.runOnIdle { focusRequester.freeFocus() }
+
+        // Assert.
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
-
-            // Assert.
             assertThat(success).isFalse()
             assertThat(focusState.hasFocus).isTrue()
         }
@@ -94,42 +98,26 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Captured))
+                    .focusTarget()
             )
         }
-
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
+            focusRequester.requestFocus()
+            focusRequester.captureFocus()
+            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusState.isCaptured).isTrue()
+        }
 
-            // Assert.
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.freeFocus()
+        }
+
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isTrue()
             assertThat(focusState.isFocused).isTrue()
-        }
-    }
-
-    @Test
-    fun deactivated_freeFocus_retainFocusAsDeactivated() {
-        // Arrange.
-        lateinit var focusState: FocusState
-        val focusRequester = FocusRequester()
-        rule.setFocusableContent {
-            Box(
-                Modifier
-                    .onFocusChanged { focusState = it }
-                    .focusRequester(focusRequester)
-                    .focusProperties { canFocus = false }
-                    .focusTarget(FocusModifier(Inactive))
-            )
-        }
-
-        rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
-
-            // Assert.
-            assertThat(success).isFalse()
-            assertThat(focusState.isDeactivated).isTrue()
+            assertThat(focusState.isCaptured).isFalse()
         }
     }
 
@@ -143,20 +131,19 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
         }
 
-        rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.freeFocus()
+        }
 
-            // Assert.
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isFalse()
             assertThat(focusState.isFocused).isFalse()
         }
     }
 }
-
-private val FocusState.isDeactivated: Boolean
-    get() = (this as FocusStateImpl).isDeactivated
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
index b95a4d2..f388f84 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
@@ -568,6 +568,6 @@
             focusManager = LocalFocusManager.current
             composable()
         }
-        rule.runOnIdle { (focusManager as FocusManagerImpl).takeFocus() }
+        rule.runOnIdle { (focusManager as FocusOwnerImpl).takeFocus() }
     }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
index 081774d..74a02fcf 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
@@ -573,6 +573,6 @@
             focusManager = LocalFocusManager.current
             composable()
         }
-        rule.runOnIdle { (focusManager as FocusManagerImpl).takeFocus() }
+        rule.runOnIdle { (focusManager as FocusOwnerImpl).takeFocus() }
     }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/RequestFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/RequestFocusTest.kt
index f6c8012..2f3e151 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/RequestFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/RequestFocusTest.kt
@@ -21,8 +21,6 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -41,19 +39,26 @@
     @Test
     fun active_isUnchanged() {
         // Arrange.
-        val focusModifier = FocusModifier(Active)
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(focusModifier))
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
         }
+        rule.runOnIdle { focusRequester.requestFocus() }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusModifier.focusState).isEqualTo(Active)
+            assertThat(focusState).isEqualTo(Active)
         }
     }
 
@@ -61,545 +66,698 @@
     fun captured_isUnchanged() {
 
         // Arrange.
-        val focusModifier = FocusModifier(Captured)
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(focusModifier))
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            focusRequester.captureFocus()
         }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusModifier.focusState).isEqualTo(Captured)
+            assertThat(focusState).isEqualTo(Captured)
         }
     }
 
     @Test
     fun deactivated_isUnchanged() {
         // Arrange.
-        val focusModifier = FocusModifier(Inactive)
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
             Box(Modifier
+                .focusRequester(focusRequester)
                 .focusProperties { canFocus = false }
-                .focusTarget(focusModifier)
+                .onFocusChanged { focusState = it }
+                .focusTarget()
             )
         }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusModifier.focusState).isEqualTo(Deactivated)
-        }
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun activeParent_withNoFocusedChild_throwsException() {
-        // Arrange.
-        val focusModifier = FocusModifier(ActiveParent)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(focusModifier))
-        }
-
-        // Act.
-        rule.runOnIdle {
-            focusModifier.requestFocus()
+            assertThat(focusState).isEqualTo(Inactive)
         }
     }
 
     @Test
     fun activeParent_propagateFocus() {
         // Arrange.
-        val focusModifier = FocusModifier(ActiveParent)
-        val childFocusModifier = FocusModifier(Active)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var childFocusState: FocusState
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(focusModifier)) {
-                Box(Modifier.focusTarget(childFocusModifier))
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()) {
+                Box(
+                    Modifier
+                        .focusRequester(initialFocus)
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget())
             }
         }
         rule.runOnIdle {
-            focusModifier.focusedChild = childFocusModifier
+            initialFocus.requestFocus()
         }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusModifier.focusState).isEqualTo(Active)
-            assertThat(focusModifier.focusedChild).isNull()
-            assertThat(childFocusModifier.focusState).isEqualTo(Inactive)
-        }
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun deactivatedParent_withNoFocusedChild_throwsException() {
-        // Arrange.
-        val focusModifier = FocusModifier(DeactivatedParent)
-        rule.setFocusableContent {
-            Box(Modifier.focusTarget(focusModifier))
-        }
-
-        // Act.
-        rule.runOnIdle {
-            focusModifier.requestFocus()
+            assertThat(focusState).isEqualTo(Active)
+            assertThat(childFocusState).isEqualTo(Inactive)
         }
     }
 
     @Test
     fun deactivatedParent_propagateFocus() {
         // Arrange.
-        val focusModifier = FocusModifier(ActiveParent)
-        val childFocusModifier = FocusModifier(Active)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
+        lateinit var childFocusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier
-                .focusProperties { canFocus = false }
-                .focusTarget(focusModifier)
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
             ) {
-                Box(Modifier.focusTarget(childFocusModifier))
+                Box(
+                    Modifier
+                        .focusRequester(initialFocus)
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget()
+                )
             }
         }
         rule.runOnIdle {
-            focusModifier.focusedChild = childFocusModifier
+            initialFocus.requestFocus()
         }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
             // Unchanged.
-            assertThat(focusModifier.focusState).isEqualTo(DeactivatedParent)
-            assertThat(childFocusModifier.focusState).isEqualTo(Active)
+            assertThat(focusState).isEqualTo(ActiveParent)
+            assertThat(childFocusState).isEqualTo(Active)
         }
     }
 
     @Test
     fun deactivatedParent_activeChild_propagateFocus() {
         // Arrange.
-        val focusModifier = FocusModifier(ActiveParent)
-        val childFocusModifier = FocusModifier(Active)
-        val grandchildFocusModifier = FocusModifier(Inactive)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+
+        lateinit var focusState: FocusState
+        lateinit var childFocusState: FocusState
+        lateinit var grandChildFocusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier
-                .focusProperties { canFocus = false }
-                .focusTarget(focusModifier)
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
             ) {
-                Box(Modifier.focusTarget(childFocusModifier)) {
-                    Box(Modifier.focusTarget(grandchildFocusModifier))
+                Box(
+                    Modifier
+                        .focusRequester(initialFocus)
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget()
+                ) {
+                    Box(
+                        Modifier
+                            .onFocusChanged { grandChildFocusState = it }
+                            .focusTarget()
+                    )
                 }
             }
         }
         rule.runOnIdle {
-            focusModifier.focusedChild = childFocusModifier
+            initialFocus.requestFocus()
         }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-                    assertThat(focusModifier.focusState).isEqualTo(DeactivatedParent)
-                    assertThat(childFocusModifier.focusState).isEqualTo(Active)
-                    assertThat(childFocusModifier.focusedChild).isNull()
-                    assertThat(grandchildFocusModifier.focusState).isEqualTo(Inactive)
+            assertThat(focusState).isEqualTo(ActiveParent)
+            assertThat(childFocusState).isEqualTo(Active)
+            assertThat(grandChildFocusState).isEqualTo(Inactive)
         }
     }
 
     @Test
     fun inactiveRoot_propagateFocusSendsRequestToOwner_systemCanGrantFocus() {
         // Arrange.
-        val rootFocusModifier = FocusModifier(Inactive)
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(rootFocusModifier))
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
         }
 
         // Act.
         rule.runOnIdle {
-            rootFocusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(rootFocusModifier.focusState).isEqualTo(Active)
+            assertThat(focusState).isEqualTo(Active)
         }
     }
 
     @Test
     fun inactiveRootWithChildren_propagateFocusSendsRequestToOwner_systemCanGrantFocus() {
         // Arrange.
-        val rootFocusModifier = FocusModifier(Inactive)
-        val childFocusModifier = FocusModifier(Inactive)
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
+        lateinit var childFocusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(rootFocusModifier)) {
-                Box(Modifier.focusTarget(childFocusModifier))
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget())
             }
         }
 
         // Act.
         rule.runOnIdle {
-            rootFocusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-                    assertThat(rootFocusModifier.focusState).isEqualTo(Active)
-                    assertThat(childFocusModifier.focusState).isEqualTo(Inactive)
+            assertThat(focusState).isEqualTo(Active)
+            assertThat(childFocusState).isEqualTo(Inactive)
         }
     }
 
     @Test
     fun inactiveNonRootWithChildren() {
         // Arrange.
-        val parentFocusModifier = FocusModifier(Active)
-        val focusModifier = FocusModifier(Inactive)
-        val childFocusModifier = FocusModifier(Inactive)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
+        lateinit var childFocusState: FocusState
+        lateinit var parentFocusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(parentFocusModifier)) {
-                Box(Modifier.focusTarget(focusModifier)) {
-                    Box(Modifier.focusTarget(childFocusModifier))
+            Box(
+                Modifier
+                    .focusRequester(initialFocus)
+                    .onFocusChanged { parentFocusState = it }
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .focusRequester(focusRequester)
+                        .onFocusChanged { focusState = it }
+                        .focusTarget()
+                ) {
+                    Box(
+                        Modifier
+                            .onFocusChanged { childFocusState = it }
+                            .focusTarget()
+                    )
                 }
             }
         }
+        rule.runOnIdle { initialFocus.requestFocus() }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(parentFocusModifier.focusState).isEqualTo(ActiveParent)
-                    assertThat(focusModifier.focusState).isEqualTo(Active)
-                    assertThat(childFocusModifier.focusState).isEqualTo(Inactive)
+            assertThat(parentFocusState).isEqualTo(ActiveParent)
+            assertThat(focusState).isEqualTo(Active)
+            assertThat(childFocusState).isEqualTo(Inactive)
         }
     }
 
     @Test
     fun rootNode() {
         // Arrange.
-        val rootFocusModifier = FocusModifier(Inactive)
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(rootFocusModifier))
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget())
         }
 
         // Act.
         rule.runOnIdle {
-            rootFocusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(rootFocusModifier.focusState).isEqualTo(Active)
+            assertThat(focusState).isEqualTo(Active)
         }
     }
 
     @Test
     fun rootNodeWithChildren() {
         // Arrange.
-        val rootFocusModifier = FocusModifier(Inactive)
-        val childFocusModifier = FocusModifier(Inactive)
+        lateinit var focusState: FocusState
+        val focusRequester = FocusRequester()
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(rootFocusModifier)) {
-                Box(Modifier.focusTarget(childFocusModifier))
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            ) {
+                Box(Modifier.focusTarget())
             }
         }
 
         // Act.
         rule.runOnIdle {
-            rootFocusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(rootFocusModifier.focusState).isEqualTo(Active)
+            assertThat(focusState).isEqualTo(Active)
         }
     }
 
     @Test
     fun parentNodeWithNoFocusedAncestor() {
         // Arrange.
-        val grandParentFocusModifier = FocusModifier(Inactive)
-        val parentFocusModifier = FocusModifier(Inactive)
-        val childFocusModifier = FocusModifier(Inactive)
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(grandParentFocusModifier)) {
-                Box(Modifier.focusTarget(parentFocusModifier)) {
-                    Box(Modifier.focusTarget(childFocusModifier))
+            Box(Modifier.focusTarget()) {
+                Box(
+                    Modifier
+                        .focusRequester(focusRequester)
+                        .onFocusChanged { focusState = it }
+                        .focusTarget()
+                ) {
+                    Box(Modifier.focusTarget())
                 }
             }
         }
 
         // Act.
         rule.runOnIdle {
-            parentFocusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(parentFocusModifier.focusState).isEqualTo(Active)
+            assertThat(focusState).isEqualTo(Active)
         }
     }
 
     @Test
     fun parentNodeWithNoFocusedAncestor_childRequestsFocus() {
         // Arrange.
-        val grandParentFocusModifier = FocusModifier(Inactive)
-        val parentFocusModifier = FocusModifier(Inactive)
-        val childFocusModifier = FocusModifier(Inactive)
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(grandParentFocusModifier)) {
-                Box(Modifier.focusTarget(parentFocusModifier)) {
-                    Box(Modifier.focusTarget(childFocusModifier))
+            Box(Modifier.focusTarget()) {
+                Box(
+                    Modifier
+                        .onFocusChanged { focusState = it }
+                        .focusTarget()) {
+                    Box(
+                        Modifier
+                            .focusRequester(focusRequester)
+                            .focusTarget())
                 }
             }
         }
 
         // Act.
         rule.runOnIdle {
-            childFocusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
+
         // Assert.
         rule.runOnIdle {
-            assertThat(parentFocusModifier.focusState).isEqualTo(ActiveParent)
+            assertThat(focusState).isEqualTo(ActiveParent)
         }
     }
 
     @Test
     fun childNodeWithNoFocusedAncestor() {
         // Arrange.
-        val grandParentFocusModifier = FocusModifier(Inactive)
-        val parentFocusModifier = FocusModifier(Inactive)
-        val childFocusModifier = FocusModifier(Inactive)
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(grandParentFocusModifier)) {
-                Box(Modifier.focusTarget(parentFocusModifier)) {
-                    Box(Modifier.focusTarget(childFocusModifier))
+            Box(Modifier.focusTarget()) {
+                Box(Modifier.focusTarget()) {
+                    Box(
+                        Modifier
+                            .focusRequester(focusRequester)
+                            .onFocusChanged { focusState = it }
+                            .focusTarget())
                 }
             }
         }
 
         // Act.
         rule.runOnIdle {
-            childFocusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(childFocusModifier.focusState).isEqualTo(Active)
+            assertThat(focusState).isEqualTo(Active)
         }
     }
 
     @Test
     fun requestFocus_parentIsFocused() {
         // Arrange.
-        val parentFocusModifier = FocusModifier(Active)
-        val focusModifier = FocusModifier(Inactive)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var parentFocusState: FocusState
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(parentFocusModifier)) {
-                Box(Modifier.focusTarget(focusModifier))
+            Box(
+                Modifier
+                    .focusRequester(initialFocus)
+                    .onFocusChanged { parentFocusState = it }
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .focusRequester(focusRequester)
+                        .onFocusChanged { focusState = it }
+                        .focusTarget()
+                )
             }
         }
+        rule.runOnIdle { initialFocus.requestFocus() }
 
         // After executing requestFocus, siblingNode will be 'Active'.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(parentFocusModifier.focusState).isEqualTo(ActiveParent)
-            assertThat(focusModifier.focusState).isEqualTo(Active)
+            assertThat(parentFocusState).isEqualTo(ActiveParent)
+            assertThat(focusState).isEqualTo(Active)
         }
     }
 
     @Test
     fun requestFocus_childIsFocused() {
         // Arrange.
-        val parentFocusModifier = FocusModifier(ActiveParent)
-        val focusModifier = FocusModifier(Active)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var parentFocusState: FocusState
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(parentFocusModifier)) {
-                Box(Modifier.focusTarget(focusModifier))
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { parentFocusState = it }
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .focusRequester(initialFocus)
+                        .onFocusChanged { focusState = it }
+                        .focusTarget())
             }
         }
-        rule.runOnIdle {
-            parentFocusModifier.focusedChild = focusModifier
-        }
+        rule.runOnIdle { initialFocus.requestFocus() }
 
         // Act.
         rule.runOnIdle {
-            parentFocusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(parentFocusModifier.focusState).isEqualTo(Active)
-                    assertThat(focusModifier.focusState).isEqualTo(Inactive)
+            assertThat(parentFocusState).isEqualTo(Active)
+            assertThat(focusState).isEqualTo(Inactive)
         }
     }
 
     @Test
     fun requestFocus_childHasCapturedFocus() {
         // Arrange.
-        val parentFocusModifier = FocusModifier(ActiveParent)
-        val focusModifier = FocusModifier(Captured)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
+        lateinit var childFocusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(parentFocusModifier)) {
-                Box(Modifier.focusTarget(focusModifier))
+            Box(
+                Modifier
+                    .focusRequester(focusRequester)
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .focusRequester(initialFocus)
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget()
+                )
             }
         }
         rule.runOnIdle {
-            parentFocusModifier.focusedChild = focusModifier
+            initialFocus.requestFocus()
+            initialFocus.captureFocus()
         }
 
         // Act.
         rule.runOnIdle {
-            parentFocusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(parentFocusModifier.focusState).isEqualTo(ActiveParent)
-            assertThat(focusModifier.focusState).isEqualTo(Captured)
+            assertThat(focusState).isEqualTo(ActiveParent)
+            assertThat(childFocusState).isEqualTo(Captured)
         }
     }
 
     @Test
     fun requestFocus_siblingIsFocused() {
         // Arrange.
-        val parentFocusModifier = FocusModifier(ActiveParent)
-        val focusModifier = FocusModifier(Inactive)
-        val siblingModifier = FocusModifier(Active)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var parentFocusState: FocusState
+        lateinit var focusState: FocusState
+        lateinit var siblingFocusState: FocusState
+
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(parentFocusModifier)) {
-                Box(Modifier.focusTarget(focusModifier))
-                Box(Modifier.focusTarget(siblingModifier))
+            Box(
+                Modifier
+                    .onFocusChanged { parentFocusState = it }
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .focusRequester(focusRequester)
+                        .onFocusChanged { focusState = it }
+                        .focusTarget()
+                )
+                Box(
+                    Modifier
+                        .focusRequester(initialFocus)
+                        .onFocusChanged { siblingFocusState = it }
+                        .focusTarget()
+                )
             }
         }
-        rule.runOnIdle {
-            parentFocusModifier.focusedChild = siblingModifier
-        }
+        rule.runOnIdle { initialFocus.requestFocus() }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(parentFocusModifier.focusState).isEqualTo(ActiveParent)
-            assertThat(focusModifier.focusState).isEqualTo(Active)
-            assertThat(siblingModifier.focusState).isEqualTo(Inactive)
+            assertThat(parentFocusState).isEqualTo(ActiveParent)
+            assertThat(focusState).isEqualTo(Active)
+            assertThat(siblingFocusState).isEqualTo(Inactive)
         }
     }
 
     @Test
     fun requestFocus_siblingHasCapturedFocused() {
         // Arrange.
-        val parentFocusModifier = FocusModifier(ActiveParent)
-        val focusModifier = FocusModifier(Inactive)
-        val siblingModifier = FocusModifier(Captured)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var parentFocusState: FocusState
+        lateinit var focusState: FocusState
+        lateinit var siblingFocusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(parentFocusModifier)) {
-                Box(Modifier.focusTarget(focusModifier))
-                Box(Modifier.focusTarget(siblingModifier))
+            Box(
+                Modifier
+                    .onFocusChanged { parentFocusState = it }
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .focusRequester(focusRequester)
+                        .onFocusChanged { focusState = it }
+                        .focusTarget())
+                Box(
+                    Modifier
+                        .focusRequester(initialFocus)
+                        .onFocusChanged { siblingFocusState = it }
+                        .focusTarget())
             }
         }
         rule.runOnIdle {
-            parentFocusModifier.focusedChild = siblingModifier
+            initialFocus.requestFocus()
+            initialFocus.captureFocus()
         }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(parentFocusModifier.focusState).isEqualTo(ActiveParent)
-            assertThat(focusModifier.focusState).isEqualTo(Inactive)
-            assertThat(siblingModifier.focusState).isEqualTo(Captured)
+            assertThat(parentFocusState).isEqualTo(ActiveParent)
+            assertThat(focusState).isEqualTo(Inactive)
+            assertThat(siblingFocusState).isEqualTo(Captured)
         }
     }
 
     @Test
     fun requestFocus_cousinIsFocused() {
         // Arrange.
-        val grandParentModifier = FocusModifier(ActiveParent)
-        val parentModifier = FocusModifier(Inactive)
-        val focusModifier = FocusModifier(Inactive)
-        val auntModifier = FocusModifier(ActiveParent)
-        val cousinModifier = FocusModifier(Active)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(grandParentModifier)) {
-                Box(Modifier.focusTarget(parentModifier)) {
-                    Box(Modifier.focusTarget(focusModifier))
+            Box(Modifier.focusTarget()) {
+                Box(Modifier.focusTarget()) {
+                    Box(
+                        Modifier
+                            .focusRequester(focusRequester)
+                            .onFocusChanged { focusState = it }
+                            .focusTarget()
+                    )
                 }
-                Box(Modifier.focusTarget(auntModifier)) {
-                    Box(Modifier.focusTarget(cousinModifier))
+                Box(Modifier.focusTarget()) {
+                    Box(
+                        Modifier
+                            .focusRequester(initialFocus)
+                            .focusTarget())
                 }
             }
         }
         rule.runOnIdle {
-            grandParentModifier.focusedChild = auntModifier
-            auntModifier.focusedChild = cousinModifier
-        }
-
-        // Verify Setup.
-        rule.runOnIdle {
-            assertThat(cousinModifier.focusState).isEqualTo(Active)
-            assertThat(focusModifier.focusState).isEqualTo(Inactive)
+            initialFocus.requestFocus()
         }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(cousinModifier.focusState).isEqualTo(Inactive)
-            assertThat(focusModifier.focusState).isEqualTo(Active)
+            assertThat(focusState).isEqualTo(Active)
         }
     }
 
     @Test
     fun requestFocus_grandParentIsFocused() {
         // Arrange.
-        val grandParentModifier = FocusModifier(Active)
-        val parentModifier = FocusModifier(Inactive)
-        val focusModifier = FocusModifier(Inactive)
+        val initialFocus = FocusRequester()
+        val focusRequester = FocusRequester()
+        lateinit var grandParentFocusState: FocusState
+        lateinit var parentFocusState: FocusState
+        lateinit var focusState: FocusState
         rule.setFocusableContent {
-            Box(Modifier.focusTarget(grandParentModifier)) {
-                Box(Modifier.focusTarget(parentModifier)) {
-                    Box(Modifier.focusTarget(focusModifier))
+            Box(
+                Modifier
+                    .focusRequester(initialFocus)
+                    .onFocusChanged { grandParentFocusState = it }
+                    .focusTarget()
+            ) {
+                Box(
+                    Modifier
+                        .onFocusChanged { parentFocusState = it }
+                        .focusTarget()
+                ) {
+                    Box(
+                        Modifier
+                            .focusRequester(focusRequester)
+                            .onFocusChanged { focusState = it }
+                            .focusTarget()
+                    )
                 }
             }
         }
+        rule.runOnIdle { initialFocus.requestFocus() }
 
         // Act.
         rule.runOnIdle {
-            focusModifier.requestFocus()
+            focusRequester.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(grandParentModifier.focusState).isEqualTo(ActiveParent)
-            assertThat(parentModifier.focusState).isEqualTo(ActiveParent)
-            assertThat(focusModifier.focusState).isEqualTo(Active)
+            assertThat(grandParentFocusState).isEqualTo(ActiveParent)
+            assertThat(parentFocusState).isEqualTo(ActiveParent)
+            assertThat(focusState).isEqualTo(Active)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ReusedFocusRequesterCaptureFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ReusedFocusRequesterCaptureFocusTest.kt
index 84ee29a..1feafc4 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ReusedFocusRequesterCaptureFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ReusedFocusRequesterCaptureFocusTest.kt
@@ -18,9 +18,6 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusStateImpl.Active
-import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -45,15 +42,18 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Active))
+                    .focusTarget()
             )
         }
+        rule.runOnIdle { focusRequester.requestFocus() }
 
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.captureFocus()
+        }
+
+        // Assert.
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.captureFocus()
-
-            // Assert.
             assertThat(success).isTrue()
             assertThat(focusState.isCaptured).isTrue()
         }
@@ -69,15 +69,19 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Captured))
+                    .focusTarget()
             )
         }
-
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.captureFocus()
+            focusRequester.requestFocus()
+            focusRequester.captureFocus()
+        }
 
-            // Assert.
+        // Act.
+        val success = rule.runOnIdle { focusRequester.captureFocus() }
+
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isTrue()
             assertThat(focusState.isCaptured).isTrue()
         }
@@ -93,15 +97,17 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
         }
 
-        rule.runOnIdle {
-            // Act.
-            val success = focusRequester.captureFocus()
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.captureFocus()
+        }
 
-            // Assert.
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isFalse()
             assertThat(focusState.isFocused).isFalse()
         }
@@ -112,27 +118,32 @@
         // Arrange.
         lateinit var focusState1: FocusState
         lateinit var focusState2: FocusState
+        val initialFocus = FocusRequester()
         val focusRequester = FocusRequester()
         rule.setFocusableContent {
             Box(
                 Modifier
                     .onFocusChanged { focusState1 = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
             Box(
                 Modifier
                     .onFocusChanged { focusState2 = it }
+                    .focusRequester(initialFocus)
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Active))
+                    .focusTarget()
             )
         }
+        rule.runOnIdle { initialFocus.requestFocus() }
 
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.captureFocus()
+        }
+
+        // Assert.
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.captureFocus()
-
-            // Assert.
             assertThat(success).isTrue()
             assertThat(focusState1.isFocused).isFalse()
             assertThat(focusState2.isCaptured).isTrue()
@@ -144,27 +155,33 @@
         // Arrange.
         lateinit var focusState1: FocusState
         lateinit var focusState2: FocusState
+        val initialFocus = FocusRequester()
         val focusRequester = FocusRequester()
         rule.setFocusableContent {
             Box(
                 Modifier
                     .onFocusChanged { focusState1 = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
             Box(
                 Modifier
                     .onFocusChanged { focusState2 = it }
+                    .focusRequester(initialFocus)
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Captured))
+                    .focusTarget()
             )
         }
-
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.captureFocus()
+            initialFocus.requestFocus()
+            initialFocus.captureFocus()
+        }
 
-            // Assert.
+        // Act.
+        val success = rule.runOnIdle { focusRequester.captureFocus() }
+
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isTrue()
             assertThat(focusState1.isFocused).isFalse()
             assertThat(focusState2.isCaptured).isTrue()
@@ -182,21 +199,23 @@
                 Modifier
                     .onFocusChanged { focusState1 = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
             Box(
                 Modifier
                     .onFocusChanged { focusState2 = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
         }
 
-        rule.runOnIdle {
-            // Act.
-            val success = focusRequester.captureFocus()
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.captureFocus()
+        }
 
-            // Assert.
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isFalse()
             assertThat(focusState1.isFocused).isFalse()
             assertThat(focusState2.isFocused).isFalse()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ReusedFocusRequesterFreeFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ReusedFocusRequesterFreeFocusTest.kt
index 9805036..f44e08a 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ReusedFocusRequesterFreeFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/ReusedFocusRequesterFreeFocusTest.kt
@@ -18,9 +18,6 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusStateImpl.Active
-import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -45,15 +42,18 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Active))
+                    .focusTarget()
             )
         }
+        rule.runOnIdle { focusRequester.requestFocus() }
 
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.freeFocus()
+        }
+
+        // Assert.
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
-
-            // Assert.
             assertThat(success).isTrue()
             assertThat(focusState.isFocused).isTrue()
         }
@@ -69,15 +69,19 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Captured))
+                    .focusTarget()
             )
         }
-
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
+            focusRequester.requestFocus()
+            focusRequester.captureFocus()
+        }
 
-            // Assert.
+        // Act.
+        val success = rule.runOnIdle { focusRequester.freeFocus() }
+
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isTrue()
             assertThat(focusState.isFocused).isTrue()
         }
@@ -93,15 +97,17 @@
                 Modifier
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
         }
 
-        rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.freeFocus()
+        }
 
-            // Assert.
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isFalse()
             assertThat(focusState.isFocused).isFalse()
         }
@@ -112,27 +118,32 @@
         // Arrange.
         lateinit var focusState1: FocusState
         lateinit var focusState2: FocusState
+        val initialFocus = FocusRequester()
         val focusRequester = FocusRequester()
         rule.setFocusableContent {
             Box(
                 Modifier
                     .onFocusChanged { focusState1 = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
             Box(
                 Modifier
                     .onFocusChanged { focusState2 = it }
+                    .focusRequester(initialFocus)
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Active))
+                    .focusTarget()
             )
         }
+        rule.runOnIdle { initialFocus.requestFocus() }
 
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.freeFocus()
+        }
+
+        // Assert.
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
-
-            // Assert.
             assertThat(success).isTrue()
             assertThat(focusState1.isFocused).isFalse()
             assertThat(focusState2.isFocused).isTrue()
@@ -144,27 +155,35 @@
         // Arrange.
         lateinit var focusState1: FocusState
         lateinit var focusState2: FocusState
+        val initialFocus = FocusRequester()
         val focusRequester = FocusRequester()
         rule.setFocusableContent {
             Box(
                 Modifier
                     .onFocusChanged { focusState1 = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
             Box(
                 Modifier
                     .onFocusChanged { focusState2 = it }
+                    .focusRequester(initialFocus)
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Captured))
+                    .focusTarget()
             )
         }
-
         rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
+            initialFocus.requestFocus()
+            initialFocus.captureFocus()
+        }
 
-            // Assert.
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.freeFocus()
+        }
+
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isTrue()
             assertThat(focusState1.isFocused).isFalse()
             assertThat(focusState2.isFocused).isTrue()
@@ -182,21 +201,23 @@
                 Modifier
                     .onFocusChanged { focusState1 = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
             Box(
                 Modifier
                     .onFocusChanged { focusState2 = it }
                     .focusRequester(focusRequester)
-                    .focusTarget(FocusModifier(Inactive))
+                    .focusTarget()
             )
         }
 
-        rule.runOnIdle {
-            // Act.
-            val success = focusRequester.freeFocus()
+        // Act.
+        val success = rule.runOnIdle {
+            focusRequester.freeFocus()
+        }
 
-            // Assert.
+        // Assert.
+        rule.runOnIdle {
             assertThat(success).isFalse()
             assertThat(focusState1.isFocused).isFalse()
             assertThat(focusState2.isFocused).isFalse()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
index cfa3544..3350d69 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.input.focus
 
+import android.view.KeyEvent as AndroidKeyEvent
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.runtime.Composable
@@ -25,21 +26,26 @@
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.focusTarget
 import androidx.compose.ui.focus.setFocusableContent
+import androidx.compose.ui.input.focus.FocusAwareEventPropagationTest.NodeType.KeyInput
+import androidx.compose.ui.input.focus.FocusAwareEventPropagationTest.NodeType.RotaryInput
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyInputInputModifierNodeImpl
+import androidx.compose.ui.input.rotary.RotaryInputModifierNodeImpl
+import androidx.compose.ui.input.rotary.RotaryScrollEvent
+import androidx.compose.ui.node.modifierElementOf
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyPress
+import androidx.compose.ui.test.performRotaryScrollInput
 import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import androidx.compose.ui.input.rotary.RotaryScrollEvent as FocusAwareTestEvent
-import androidx.compose.ui.input.rotary.onPreRotaryScrollEvent as onPreFocusAwareEvent
-import androidx.compose.ui.input.rotary.onRotaryScrollEvent as onFocusAwareEvent
-import androidx.compose.ui.test.performRotaryScrollInput as performFocusAwareInput
+import org.junit.runners.Parameterized
 
 /**
  * Focus-aware event propagation test.
@@ -50,20 +56,31 @@
  */
 @OptIn(ExperimentalComposeUiApi::class)
 @MediumTest
-@RunWith(AndroidJUnit4::class)
-class FocusAwareEventPropagationTest {
+@RunWith(Parameterized::class)
+class FocusAwareEventPropagationTest(private val nodeType: NodeType) {
     @get:Rule
     val rule = createComposeRule()
 
     @OptIn(ExperimentalComposeUiApi::class)
-    private val sentEvent: FocusAwareTestEvent =
-        FocusAwareTestEvent(1f, 2f, 3L)
-    private var receivedEvent: FocusAwareTestEvent? = null
+    private val sentEvent: Any = when (nodeType) {
+        KeyInput ->
+            KeyEvent(AndroidKeyEvent(AndroidKeyEvent.ACTION_DOWN, AndroidKeyEvent.KEYCODE_A))
+        RotaryInput ->
+            RotaryScrollEvent(1f, 1f, 0L)
+    }
+    private var receivedEvent: Any? = null
     private val initialFocus = FocusRequester()
 
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "node = {0}")
+        fun initParameters() = arrayOf(KeyInput, RotaryInput)
+    }
+
     @Test
     fun noFocusable_doesNotDeliverEvent() {
         // Arrange.
+        var error: IllegalStateException? = null
         rule.setContent {
             Box(
                 modifier = Modifier.onFocusAwareEvent {
@@ -74,15 +91,24 @@
         }
 
         // Act.
-        rule.onRoot().performFocusAwareInput(sentEvent)
+        try {
+            rule.onRoot().performFocusAwareInput(sentEvent)
+        } catch (exception: IllegalStateException) {
+            error = exception
+        }
 
         // Assert.
         assertThat(receivedEvent).isNull()
+        when (nodeType) {
+            KeyInput -> assertThat(error!!.message).contains("do not have an active focus target")
+            RotaryInput -> assertThat(error).isNull()
+        }
     }
 
     @Test
     fun unfocusedFocusable_doesNotDeliverEvent() {
         // Arrange.
+        var error: IllegalStateException? = null
         rule.setFocusableContent {
             Box(
                 modifier = Modifier
@@ -95,10 +121,18 @@
         }
 
         // Act.
-        rule.onRoot().performFocusAwareInput(sentEvent)
+        try {
+            rule.onRoot().performFocusAwareInput(sentEvent)
+        } catch (exception: IllegalStateException) {
+            error = exception
+        }
 
         // Assert.
         assertThat(receivedEvent).isNull()
+        when (nodeType) {
+            KeyInput -> assertThat(error!!.message).contains("do not have an active focus target")
+            RotaryInput -> assertThat(error).isNull()
+        }
     }
 
     @Test
@@ -120,7 +154,10 @@
         rule.onRoot().performFocusAwareInput(sentEvent)
 
         // Assert.
-        assertThat(receivedEvent).isNull()
+        when (nodeType) {
+            KeyInput -> assertThat(receivedEvent).isEqualTo(sentEvent)
+            RotaryInput -> assertThat(receivedEvent).isNull()
+        }
     }
 
     @Test
@@ -141,7 +178,10 @@
         rule.onRoot().performFocusAwareInput(sentEvent)
 
         // Assert.
-        assertThat(receivedEvent).isNull()
+        when (nodeType) {
+            KeyInput -> assertThat(receivedEvent).isEqualTo(sentEvent)
+            RotaryInput -> assertThat(receivedEvent).isNull()
+        }
     }
 
     @Test
@@ -162,15 +202,11 @@
         rule.onRoot().performFocusAwareInput(sentEvent)
 
         // Assert.
-        rule.runOnIdle {
-            // performFocusAwareInput generates a vertical scroll
-            assertThat(sentEvent.verticalScrollPixels)
-                .isEqualTo(receivedEvent?.verticalScrollPixels)
-        }
+        rule.runOnIdle { assertThat(sentEvent).isEqualTo(receivedEvent) }
     }
 
     @Test
-    fun onPreviewKeyEvent_triggered() {
+    fun onPreFocusAwareEvent_triggered() {
         // Arrange.
         ContentWithInitialFocus {
             Box(
@@ -187,11 +223,7 @@
         rule.onRoot().performFocusAwareInput(sentEvent)
 
         // Assert.
-        rule.runOnIdle {
-            // performFocusAwareInput generates a vertical scroll
-            assertThat(sentEvent.verticalScrollPixels)
-                .isEqualTo(receivedEvent?.verticalScrollPixels)
-        }
+        rule.runOnIdle { assertThat(sentEvent).isEqualTo(receivedEvent) }
     }
 
     @Test
@@ -472,10 +504,19 @@
         .then(if (initiallyFocused) Modifier.focusRequester(initialFocus) else Modifier)
         .focusTarget()
 
-    private fun SemanticsNodeInteraction.performFocusAwareInput(sentEvent: FocusAwareTestEvent) {
-        @OptIn(ExperimentalTestApi::class)
-        performFocusAwareInput {
-            rotateToScrollVertically(sentEvent.verticalScrollPixels)
+    private fun SemanticsNodeInteraction.performFocusAwareInput(sentEvent: Any) {
+        when (nodeType) {
+            KeyInput -> {
+                check(sentEvent is KeyEvent)
+                performKeyPress(sentEvent)
+            }
+            RotaryInput -> {
+                check(sentEvent is RotaryScrollEvent)
+                @OptIn(ExperimentalTestApi::class)
+                performRotaryScrollInput {
+                    rotateToScrollVertically(sentEvent.verticalScrollPixels)
+                }
+            }
         }
     }
 
@@ -485,4 +526,54 @@
         }
         rule.runOnIdle { initialFocus.requestFocus() }
     }
+
+    private fun Modifier.onFocusAwareEvent(onEvent: (Any) -> Boolean): Modifier = this.then(
+        when (nodeType) {
+            KeyInput -> modifierElementOf(
+                key = onEvent,
+                create = { KeyInputInputModifierNodeImpl(  },
+                update = { it. },
+                definitions = {
+                    name = "onEvent"
+                    properties["onEvent"] = onEvent
+                }
+            )
+
+            RotaryInput -> modifierElementOf(
+                key = onEvent,
+                create = { RotaryInputModifierNodeImpl(  },
+                update = { it. },
+                definitions = {
+                    name = "onEvent"
+                    properties["onEvent"] = onEvent
+                }
+            )
+        }
+    )
+
+    private fun Modifier.onPreFocusAwareEvent(onEvent: (Any) -> Boolean): Modifier = this.then(
+        when (nodeType) {
+            KeyInput -> modifierElementOf(
+                key = onEvent,
+                create = { KeyInputInputModifierNodeImpl(  },
+                update = { it. },
+                definitions = {
+                    name = "onEvent"
+                    properties["onEvent"] = onEvent
+                }
+            )
+
+            RotaryInput -> modifierElementOf(
+                key = onEvent,
+                create = { RotaryInputModifierNodeImpl(  },
+                update = { it. },
+                definitions = {
+                    name = "onEvent"
+                    properties["onEvent"] = onEvent
+                }
+            )
+        }
+    )
+
+    enum class NodeType { KeyInput, RotaryInput }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
index cc1722b..0c88d69 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
@@ -16,17 +16,20 @@
 
 package androidx.compose.ui.input.key
 
+import android.view.KeyEvent as AndroidKeyEvent
+import android.view.KeyEvent.KEYCODE_A as KeyCodeA
+import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.ACTION_UP
 import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.focus.setFocusableContent
-import android.view.KeyEvent.KEYCODE_A as KeyCodeA
-import android.view.KeyEvent as AndroidKeyEvent
-import android.view.KeyEvent.ACTION_DOWN
-import android.view.KeyEvent.ACTION_UP
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.focus.focusTarget
+import androidx.compose.ui.focus.setFocusableContent
 import androidx.compose.ui.input.key.Key.Companion.A
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
@@ -40,7 +43,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@Suppress("DEPRECATION")
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalComposeUiApi::class)
@@ -52,7 +54,7 @@
     fun noRootFocusTarget_throwsException() {
         // Arrange.
         rule.setContent {
-            Box(modifier = KeyInputModifier(null, null))
+            Box(modifier = Modifier.onKeyEvent { false })
         }
 
         // Act.
@@ -75,7 +77,9 @@
 
         // Arrange.
         rule.setFocusableContent {
-            Box(modifier = Modifier.focusTarget().onKeyEvent { true })
+            Box(modifier = Modifier
+                .focusTarget()
+                .onKeyEvent { true })
         }
 
         // Act.
@@ -219,6 +223,82 @@
     }
 
     @Test
+    fun onKeyEvent_afterUpdate() {
+        // Arrange.
+        val focusRequester = FocusRequester()
+        var keyEventFromOnKeyEvent1: KeyEvent? = null
+        var keyEventFromOnKeyEvent2: KeyEvent? = null
+        var onKeyEvent: (event: KeyEvent) -> Boolean by mutableStateOf(
+            value = {
+                keyEventFromOnKeyEvent1 = it
+                true
+            }
+        )
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onKeyEvent(onKeyEvent)
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Act.
+        rule.runOnIdle {
+            >
+                keyEventFromOnKeyEvent2 = it
+                true
+            }
+        }
+        rule.onRoot().performKeyPress(keyEvent(KeyCodeA, KeyUp))
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(keyEventFromOnKeyEvent1).isNull()
+            assertThat(keyEventFromOnKeyEvent2).isNotNull()
+        }
+    }
+
+    @Test
+    fun onPreviewKeyEvent_afterUpdate() {
+        // Arrange.
+        val focusRequester = FocusRequester()
+        var keyEventFromOnPreviewKeyEvent1: KeyEvent? = null
+        var keyEventFromOnPreviewKeyEvent2: KeyEvent? = null
+        var onPreviewKeyEvent: (event: KeyEvent) -> Boolean by mutableStateOf(
+            value = {
+                keyEventFromOnPreviewKeyEvent1 = it
+                true
+            }
+        )
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onPreviewKeyEvent(onPreviewKeyEvent)
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Act.
+        rule.runOnIdle {
+            >
+                keyEventFromOnPreviewKeyEvent2 = it
+                true
+            }
+        }
+        rule.onRoot().performKeyPress(keyEvent(KeyCodeA, KeyUp))
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(keyEventFromOnPreviewKeyEvent1).isNull()
+            assertThat(keyEventFromOnPreviewKeyEvent2).isNotNull()
+        }
+    }
+
+    @Test
     fun parent_child() {
         // Arrange.
         val focusRequester = FocusRequester()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
index 2d2932e..272cf37 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
@@ -23,7 +23,7 @@
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
 import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusOwner
 import androidx.compose.ui.geometry.MutableRect
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
@@ -3644,7 +3644,7 @@
         get() = TODO("Not yet implemented")
     override val pointerIconService: PointerIconService
         get() = TODO("Not yet implemented")
-    override val focusManager: FocusManager
+    override val focusOwner: FocusOwner
         get() = TODO("Not yet implemented")
     override val windowInfo: WindowInfo
         get() = TODO("Not yet implemented")
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
index e8bf395..c56ae6e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
@@ -24,7 +24,7 @@
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
 import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusOwner
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.hapticfeedback.HapticFeedback
@@ -3307,7 +3307,7 @@
         get() = TODO("Not yet implemented")
     override val pointerIconService: PointerIconService
         get() = TODO("Not yet implemented")
-    override val focusManager: FocusManager
+    override val focusOwner: FocusOwner
         get() = TODO("Not yet implemented")
     override val windowInfo: WindowInfo
         get() = TODO("Not yet implemented")
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEventTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEventTest.kt
index e58a234..00613cd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEventTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEventTest.kt
@@ -22,11 +22,15 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.focusTarget
+import androidx.compose.ui.focus.setFocusableContent
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -214,6 +218,84 @@
         }
     }
 
+    @Test
+    fun onRotaryKeyEvent_afterUpdate() {
+        // Arrange.
+        val focusRequester = FocusRequester()
+        var keyEventFromOnKeyEvent1: RotaryScrollEvent? = null
+        var keyEventFromOnKeyEvent2: RotaryScrollEvent? = null
+        var onRotaryScrollEvent: (event: RotaryScrollEvent) -> Boolean by mutableStateOf(
+            value = {
+                keyEventFromOnKeyEvent1 = it
+                true
+            }
+        )
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onRotaryScrollEvent(onRotaryScrollEvent)
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Act.
+        rule.runOnIdle {
+            >
+                keyEventFromOnKeyEvent2 = it
+                true
+            }
+        }
+        @OptIn(ExperimentalTestApi::class)
+        rule.onRoot().performRotaryScrollInput { rotateToScrollVertically(3.0f) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(keyEventFromOnKeyEvent1).isNull()
+            assertThat(keyEventFromOnKeyEvent2).isNotNull()
+        }
+    }
+
+    @Test
+    fun onRotaryPreviewKeyEvent_afterUpdate() {
+        // Arrange.
+        val focusRequester = FocusRequester()
+        var keyEventFromOnPreRotaryScrollEvent1: RotaryScrollEvent? = null
+        var keyEventFromOnPreRotaryScrollEvent2: RotaryScrollEvent? = null
+        var onPreRotaryScrollEvent: (event: RotaryScrollEvent) -> Boolean by mutableStateOf(
+            value = {
+                keyEventFromOnPreRotaryScrollEvent1 = it
+                true
+            }
+        )
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .onPreRotaryScrollEvent(onPreRotaryScrollEvent)
+                    .focusTarget()
+            )
+        }
+        rule.runOnIdle { focusRequester.requestFocus() }
+
+        // Act.
+        rule.runOnIdle {
+            >
+                keyEventFromOnPreRotaryScrollEvent2 = it
+                true
+            }
+        }
+        @OptIn(ExperimentalTestApi::class)
+        rule.onRoot().performRotaryScrollInput { rotateToScrollVertically(3.0f) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(keyEventFromOnPreRotaryScrollEvent1).isNull()
+            assertThat(keyEventFromOnPreRotaryScrollEvent2).isNotNull()
+        }
+    }
+
     private fun Modifier.focusable(initiallyFocused: Boolean = false) = this
         .then(if (initiallyFocused) Modifier.focusRequester(initialFocus) else Modifier)
         .focusTarget()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/Helpers.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/Helpers.kt
index a24680c..0d06482 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/Helpers.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/Helpers.kt
@@ -19,7 +19,7 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
-import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusOwner
 import androidx.compose.ui.geometry.MutableRect
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
@@ -153,7 +153,7 @@
         get() = TODO("Not yet implemented")
     override val pointerIconService: PointerIconService
         get() = TODO("Not yet implemented")
-    override val focusManager: FocusManager
+    override val focusOwner: FocusOwner
         get() = TODO("Not yet implemented")
     override val windowInfo: WindowInfo
         get() = TODO("Not yet implemented")
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt
index 40d0700..f228f88 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt
@@ -21,6 +21,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusTargetModifierNode
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.dp
@@ -135,6 +136,113 @@
     }
 
     @Test
+    fun visitSubtreeIf_stopsIfWeReturnFalse() {
+        // Arrange.
+        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
+        val (node4, node5, node6) = List(3) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(modifier = modifierElementOf { node1 }) {
+                Box(modifier = modifierElementOf { node2 }) {
+                    Box(modifier = modifierElementOf { node3 })
+                    Box(modifier = modifierElementOf { node4 }) {
+                        Box(modifier = modifierElementOf { node6 })
+                    }
+                    Box(modifier = modifierElementOf { node5 })
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node1.visitSubtreeIf(Nodes.Any) {
+                visitedChildren.add(it)
+                // return false if we encounter node4
+                it != node4
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren).containsExactly(node2, node3, node4, node5).inOrder()
+    }
+
+    @Test
+    fun visitSubtreeIf_continuesIfWeReturnTrue() {
+        // Arrange.
+        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
+        val (node4, node5, node6) = List(3) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(modifier = modifierElementOf { node1 }) {
+                Box(modifier = modifierElementOf { node2 }) {
+                    Box(modifier = modifierElementOf { node3 })
+                    Box(modifier = modifierElementOf { node4 }) {
+                        Box(modifier = modifierElementOf { node6 })
+                    }
+                    Box(modifier = modifierElementOf { node5 })
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node1.visitSubtreeIf(Nodes.Any) {
+                visitedChildren.add(it)
+                true
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren).containsExactly(node2, node3, node4, node6, node5).inOrder()
+    }
+
+    @Test
+    fun visitSubtree_visitsItemsInCurrentLayoutNode() {
+        // Arrange.
+        val (node1, node2, node3, node4, node5) = List(6) { object : Modifier.Node() {} }
+        val (node6, node7, node8, node9, node10) = List(6) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                modifier = modifierElementOf { node1 }
+                    .then(modifierElementOf { node2 })
+            ) {
+                Box(
+                    modifier = modifierElementOf { node3 }
+                        .then(modifierElementOf { node4 })
+                ) {
+                    Box(
+                        modifier = modifierElementOf { node7 }
+                            .then(modifierElementOf { node8 })
+                    )
+                }
+                Box(
+                    modifier = modifierElementOf { node5 }
+                        .then(modifierElementOf { node6 })
+                ) {
+                    Box(
+                        modifier = modifierElementOf { node9 }
+                            .then(modifierElementOf { node10 })
+                    )
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node1.visitSubtreeIf(Nodes.Any) {
+                visitedChildren.add(it)
+                true
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren)
+            .containsExactly(node2, node3, node4, node7, node8, node5, node6, node9, node10)
+            .inOrder()
+    }
+
+    @Test
     fun visitAncestorWithinCurrentLayoutNode_immediateParent() {
         // Arrange.
         val (node1, node2) = List(2) { object : Modifier.Node() {} }
@@ -218,6 +326,83 @@
     }
 
     @Test
+    fun nearestAncestorInDifferentLayoutNode_nonContiguousParentLayoutNode() {
+        // Arrange.
+        val (node1, node2) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(modifier = modifierElementOf { node1 }) {
+                Box {
+                    Box(modifier = modifierElementOf { node2 })
+                }
+            }
+        }
+
+        // Act.
+        val parent = rule.runOnIdle {
+            node2.nearestAncestor(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(parent).isEqualTo(node1)
+    }
+
+    @Test
+    fun visitAncestors_sameLayoutNode_calledDuringOnDetach() {
+        // Arrange.
+        val (node1, node2) = List(5) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        val detachableNode = DetachableNode { node ->
+            node.visitAncestors(Nodes.Any) { visitedAncestors.add(it) }
+        }
+        val removeNode = mutableStateOf(false)
+        rule.setContent {
+            Box(
+                modifier = modifierElementOf { node1 }
+                    .then(modifierElementOf { node2 })
+                    .then(if (removeNode.value) Modifier else modifierElementOf { detachableNode })
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { removeNode.value = true }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(visitedAncestors)
+                .containsAtLeastElementsIn(arrayOf(node2, node1))
+                .inOrder()
+        }
+    }
+
+    @Test
+    fun visitAncestors_multipleLayoutNodes_calledDuringOnDetach() {
+        // Arrange.
+        val (node1, node2) = List(5) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        val detachableNode = DetachableNode { node ->
+            node.visitAncestors(Nodes.Any) { visitedAncestors.add(it) }
+        }
+        val removeNode = mutableStateOf(false)
+        rule.setContent {
+            Box(modifierElementOf { node1 }) {
+                Box(modifierElementOf { node2 }) {
+                    Box(if (removeNode.value) Modifier else modifierElementOf { detachableNode })
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle { removeNode.value = true }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(visitedAncestors)
+                .containsAtLeastElementsIn(arrayOf(node2, node1))
+                .inOrder()
+        }
+    }
+
+    @Test
     fun nearestAncestorWithinCurrentLayoutNode_immediateParent() {
         // Arrange.
         val (node1, node2) = List(2) { object : Modifier.Node() {} }
@@ -278,54 +463,123 @@
     }
 
     @Test
-    fun visitAncestors_sameLayoutNode_calledDuringOnDetach() {
+    fun findAncestors() {
         // Arrange.
-        val (node1, node2) = List(5) { object : Modifier.Node() {} }
-        val visitedAncestors = mutableListOf<Modifier.Node>()
-        val detachableNode = DetachableNode {
-            it.visitAncestors(Nodes.Any) { node ->
-                visitedAncestors.add(node)
-            }
-        }
-        val removeNode = mutableStateOf(false)
+        val (node1, node2, node3, node4) = List(4) { FocusTargetModifierNode() }
+        val (node5, node6, node7, node8) = List(4) { FocusTargetModifierNode() }
         rule.setContent {
             Box(
                 modifier = modifierElementOf { node1 }
                     .then(modifierElementOf { node2 })
-                    .then(if (removeNode.value) Modifier else modifierElementOf { detachableNode })
-            )
-        }
-
-        // Act.
-        rule.runOnIdle { removeNode.value = true }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(visitedAncestors)
-                .containsAtLeastElementsIn(arrayOf(node2, node1))
-                .inOrder()
-        }
-    }
-
-    @Test
-    fun nearestAncestorInDifferentLayoutNode_nonContiguousParentLayoutNode() {
-        // Arrange.
-        val (node1, node2) = List(2) { object : Modifier.Node() {} }
-        rule.setContent {
-            Box(modifier = modifierElementOf { node1 }) {
+            ) {
                 Box {
-                    Box(modifier = modifierElementOf { node2 })
+                    Box(modifier = modifierElementOf { node3 })
+                    Box(
+                        modifier = modifierElementOf { node4 }
+                            .then(modifierElementOf { node5 })
+                    ) {
+                        Box(
+                            modifier = modifierElementOf { node6 }
+                                .then(modifierElementOf { node7 })
+                        )
+                    }
+                    Box(modifier = modifierElementOf { node8 })
                 }
             }
         }
 
         // Act.
-        val parent = rule.runOnIdle {
-            node2.nearestAncestor(Nodes.Any)
+        val ancestors = rule.runOnIdle {
+            node6.ancestors(Nodes.FocusTarget)
         }
 
         // Assert.
-        assertThat(parent).isEqualTo(node1)
+        // This test returns all ancestors, even the root focus node. so we drop that one.
+        assertThat(ancestors?.dropLast(1)).containsExactly(node5, node4, node2, node1).inOrder()
+    }
+
+    @Test
+    fun firstChild_currentLayoutNode() {
+        // Arrange.
+        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                modifier = modifierElementOf { node1 }
+                    .then(modifierElementOf { node2 })
+                    .then(modifierElementOf { node3 })
+            )
+        }
+
+        // Act.
+        val child = rule.runOnIdle {
+            node1.firstChild(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(child).isEqualTo(node2)
+    }
+
+    @Test
+    fun firstChild_currentLayoutNode_nonContiguousChild() {
+        // Arrange.
+        val (node1, node2) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                modifier = modifierElementOf { node1 }
+                    .otherModifier()
+                    .then(modifierElementOf { node2 })
+            )
+        }
+
+        // Act.
+        val child = rule.runOnIdle {
+            node1.firstChild(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(child).isEqualTo(node2)
+    }
+
+    @Test
+    fun firstChild_differentLayoutNode() {
+        // Arrange.
+        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(modifier = modifierElementOf { node1 }) {
+                Box(modifier = modifierElementOf { node2 }
+                    .then(modifierElementOf { node3 }))
+            }
+        }
+
+        // Act.
+        val child = rule.runOnIdle {
+            node1.firstChild(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(child).isEqualTo(node2)
+    }
+
+    fun firstChild_differentLayoutNode_nonContiguousChild() {
+        // Arrange.
+        val (node1, node2) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(modifier = modifierElementOf { node1 }) {
+                Box {
+                    Box(modifier = Modifier.otherModifier()) {
+                        Box(modifier = modifierElementOf { node2 })
+                    }
+                }
+            }
+        }
+
+        // Act.
+        val child = rule.runOnIdle {
+            node1.firstChild(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(child).isEqualTo(node2)
     }
 
     @Test
@@ -350,20 +604,16 @@
     }
 
     private fun Modifier.otherModifier(): Modifier = this.then(Modifier)
-}
 
-@ExperimentalComposeUiApi
-internal inline fun <reified T : Modifier.Node> modifierElementOf(
-    crossinline create: () -> T,
-): Modifier = object : ModifierNodeElement<T>(null, true, {}) {
-    override fun create(): T = create()
-    override fun update(node: T): T = node
-}
+    @Suppress("ModifierInspectorInfo") // b/251831790.
+    @ExperimentalComposeUiApi
+    private inline fun <reified T : Modifier.Node> modifierElementOf(crossinline create: () -> T) =
+        modifierElementOf(create) { name = "testNode" }
 
-@OptIn(ExperimentalComposeUiApi::class)
-private class DetachableNode(val onDetach: (DetachableNode) -> Unit) : Modifier.Node() {
-    override fun onDetach() {
-        onDetach.invoke(this)
-        super.onDetach()
+    private class DetachableNode(val onDetach: (DetachableNode) -> Unit) : Modifier.Node() {
+        override fun onDetach() {
+            onDetach.invoke(this)
+            super.onDetach()
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/InvalidateSubtreeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/InvalidateSubtreeTest.kt
index b7279e2..7bc20da 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/InvalidateSubtreeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/InvalidateSubtreeTest.kt
@@ -26,6 +26,7 @@
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.dp
@@ -54,6 +55,9 @@
                 val obj = object : Modifier.Node() {}
                 invalidate = { obj.invalidateSubtree() }
                 obj
+            },
+            definitions = debugInspectorInfo {
+                name = "Invalidate Subtree Modifier.Node"
             }
         )
         rule.setContent {
@@ -103,6 +107,9 @@
                 val obj = object : Modifier.Node() {}
                 invalidate = { obj.invalidateSubtree() }
                 obj
+            },
+            definitions = debugInspectorInfo {
+                name = "Invalidate Subtree Modifier.Node"
             }
         )
         rule.setContent {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeCoordinatorInitializationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeCoordinatorInitializationTest.kt
index ea142f4..d4d1129 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeCoordinatorInitializationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeCoordinatorInitializationTest.kt
@@ -21,10 +21,9 @@
 import androidx.compose.runtime.currentRecomposeScope
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusModifier
-import androidx.compose.ui.focus.FocusStateImpl
+import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.focus.focusTarget
-import androidx.compose.ui.input.key.KeyInputModifier
+import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.input.pointer.PointerInputModifier
 import androidx.compose.ui.input.pointer.PointerInteropFilter
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -44,32 +43,20 @@
     @Test
     fun initializeIsCalledWhenFocusNodeIsCreated() {
         // Arrange.
-        val focusModifier = FocusModifier(FocusStateImpl.Inactive)
+        var focusState: FocusState? = null
 
         // Act.
         rule.setContent {
-            Box(Modifier.focusTarget(focusModifier))
+            Box(
+                Modifier
+                    .onFocusChanged { focusState = it }
+                    .focusTarget()
+            )
         }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusModifier).isNotNull()
-        }
-    }
-
-    @Test
-    fun initializeIsCalledWhenKeyInputNodeIsCreated() {
-        // Arrange.
-        val keyInputModifier = KeyInputModifier(null, null)
-
-        // Act.
-        rule.setContent {
-            Box(modifier = keyInputModifier)
-        }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(keyInputModifier.layoutNode).isNotNull()
+            assertThat(focusState).isNotNull()
         }
     }
 
@@ -90,44 +77,6 @@
         }
     }
 
-    @Test
-    fun initializeIsCalledWhenFocusNodeIsReused() {
-        // Arrange.
-        lateinit var focusModifier: FocusModifier
-        lateinit var scope: RecomposeScope
-        rule.setContent {
-            scope = currentRecomposeScope
-            focusModifier = FocusModifier(FocusStateImpl.Inactive)
-            Box(Modifier.focusTarget(focusModifier))
-        }
-
-        // Act.
-        rule.runOnIdle { scope.invalidate() }
-
-        // Assert.
-        rule.runOnIdle { assertThat(focusModifier).isNotNull() }
-    }
-
-    @Test
-    fun initializeIsCalledWhenKeyInputNodeIsReused() {
-        // Arrange.
-        lateinit var keyInputModifier: KeyInputModifier
-        lateinit var scope: RecomposeScope
-        rule.setContent {
-            scope = currentRecomposeScope
-            keyInputModifier = KeyInputModifier(null, null)
-            Box(modifier = keyInputModifier)
-        }
-
-        // Act.
-        rule.runOnIdle { scope.invalidate() }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(keyInputModifier.layoutNode).isNotNull()
-        }
-    }
-
     @ExperimentalComposeUiApi
     @Test
     fun initializeIsCalledWhenPointerInputNodeIsReused() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverNodeTest.kt
index 1bfa2ff..d6ceb13 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverNodeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverNodeTest.kt
@@ -44,7 +44,7 @@
         var callbackInvoked = false
         val observerNode = TestObserverNode { callbackInvoked = true }
         rule.setContent {
-            Box(Modifier.modifierElementOf { observerNode })
+            Box(Modifier.modifierElementOf(observerNode))
         }
 
         // Act.
@@ -66,7 +66,7 @@
         var callbackInvoked = false
         val observerNode = TestObserverNode { callbackInvoked = true }
         rule.setContent {
-            Box(Modifier.modifierElementOf { observerNode })
+            Box(Modifier.modifierElementOf(observerNode))
         }
 
         // Act.
@@ -114,7 +114,7 @@
         val observerNode = TestObserverNode { callbackInvoked = true }
         var attached by mutableStateOf(true)
         rule.setContent {
-            Box(if (attached) modifierElementOf { observerNode } else Modifier)
+            Box(if (attached) Modifier.modifierElementOf(observerNode) else Modifier)
         }
 
         // Act.
@@ -139,7 +139,7 @@
         val observerNode = TestObserverNode { callbackInvoked = true }
         var attached by mutableStateOf(true)
         rule.setContent {
-            Box(if (attached) modifierElementOf { observerNode } else Modifier)
+            Box(if (attached) Modifier.modifierElementOf(observerNode) else Modifier)
         }
 
         // Act.
@@ -161,10 +161,8 @@
     }
 
     @ExperimentalComposeUiApi
-    private inline fun <reified T : Modifier.Node> Modifier.modifierElementOf(
-        crossinline create: () -> T,
-    ): Modifier {
-        return this.then(modifierElementOf(create) { name = "testNode" })
+    private inline fun <reified T : Modifier.Node> Modifier.modifierElementOf(node: T): Modifier {
+        return this.then(modifierElementOf(create = { node }, definitions = { name = "testNode" }))
     }
 
     class TestObserverNode(
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index d329cf2..f50648d 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -73,9 +73,8 @@
 import androidx.compose.ui.focus.FocusDirection.Companion.Previous
 import androidx.compose.ui.focus.FocusDirection.Companion.Right
 import androidx.compose.ui.focus.FocusDirection.Companion.Up
-import androidx.compose.ui.focus.FocusManager
-import androidx.compose.ui.focus.FocusManagerImpl
-import androidx.compose.ui.focus.focusRect
+import androidx.compose.ui.focus.FocusOwner
+import androidx.compose.ui.focus.FocusOwnerImpl
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.CanvasHolder
@@ -101,9 +100,9 @@
 import androidx.compose.ui.input.key.Key.Companion.Tab
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
-import androidx.compose.ui.input.key.KeyInputModifier
 import androidx.compose.ui.input.key.isShiftPressed
 import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
 import androidx.compose.ui.input.key.type
 import androidx.compose.ui.input.pointer.AndroidPointerIcon
 import androidx.compose.ui.input.pointer.AndroidPointerIconType
@@ -196,9 +195,7 @@
         properties = {}
     )
 
-    private val _focusManager: FocusManagerImpl = FocusManagerImpl()
-    override val focusManager: FocusManager
-        get() = _focusManager
+    override val focusOwner: FocusOwner = FocusOwnerImpl { registerOnEndApplyChangesListener(it) }
 
     private val _windowInfo: WindowInfoImpl = WindowInfoImpl()
     override val windowInfo: WindowInfo
@@ -206,16 +203,13 @@
 
     // TODO(b/177931787) : Consider creating a KeyInputManager like we have for FocusManager so
     //  that this common logic can be used by all owners.
-    private val keyInputModifier: KeyInputModifier = KeyInputModifier(
-        >
-            val focusDirection = getFocusDirection(it)
-            if (focusDirection == null || it.type != KeyDown) return@KeyInputModifier false
+    private val keyInputModifier = Modifier.onKeyEvent {
+        val focusDirection = getFocusDirection(it)
+        if (focusDirection == null || it.type != KeyDown) return@onKeyEvent false
 
-            // Consume the key event if we moved focus.
-            focusManager.moveFocus(focusDirection)
-        },
-        >
-    )
+        // Consume the key event if we moved focus.
+        focusOwner.moveFocus(focusDirection)
+    }
 
     private val rotaryInputModifier = Modifier.onRotaryScrollEvent {
         // TODO(b/210748692): call focusManager.moveFocus() in response to rotary events.
@@ -259,7 +253,7 @@
         it.modifier = Modifier
             .then(semanticsModifier)
             .then(rotaryInputModifier)
-            .then(_focusManager.modifier)
+            .then(focusOwner.modifier)
             .then(keyInputModifier)
             .then(scrollContainerInfo)
     }
@@ -401,7 +395,6 @@
     // executed whenever the touch mode changes.
     private val touchModeChangeListener = ViewTreeObserver.OnTouchModeChangeListener { touchMode ->
         _inputModeManager.inputMode = if (touchMode) Touch else Keyboard
-        _focusManager.fetchUpdatedFocusProperties()
     }
 
     private val textInputServiceAndroid = TextInputServiceAndroid(this)
@@ -605,11 +598,11 @@
      * system for accurate focus searching and so ViewRootImpl will scroll correctly.
      */
     override fun getFocusedRect(rect: Rect) {
-        _focusManager.getActiveFocusModifier()?.focusRect()?.let {
-            rect.left = it.left.roundToInt()
-            rect.top = it.top.roundToInt()
-            rect.right = it.right.roundToInt()
-            rect.bottom = it.bottom.roundToInt()
+        focusOwner.getFocusRect()?.run {
+            rect.left = left.roundToInt()
+            rect.top = top.roundToInt()
+            rect.right = right.roundToInt()
+            rect.bottom = bottom.roundToInt()
         } ?: super.getFocusedRect(rect)
     }
 
@@ -621,9 +614,7 @@
     override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
         Log.d(FocusTag, "Owner FocusChanged($gainFocus)")
-        with(_focusManager) {
-            if (gainFocus) takeFocus() else releaseFocus()
-        }
+        if (gainFocus) focusOwner.takeFocus() else focusOwner.releaseFocus()
     }
 
     override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
@@ -646,7 +637,7 @@
     }
 
     override fun sendKeyEvent(keyEvent: KeyEvent): Boolean {
-        return keyInputModifier.processKeyInput(keyEvent)
+        return focusOwner.dispatchKeyEvent(keyEvent)
     }
 
     override fun dispatchKeyEvent(event: AndroidKeyEvent) =
@@ -1268,7 +1259,7 @@
             horizontalScrollPixels = axisValue * getScaledHorizontalScrollFactor(config, context),
             uptimeMillis = event.eventTime
         )
-        return _focusManager.getActiveFocusModifier()?.propagateRotaryEvent(rotaryEvent) ?: false
+        return focusOwner.dispatchRotaryEvent(rotaryEvent)
     }
 
     private fun handleMotionEvent(motionEvent: MotionEvent): ProcessResult {
@@ -1569,7 +1560,7 @@
         if (superclassInitComplete) {
             layoutDirectionFromInt(layoutDirection).let {
                 this.layoutDirection = it
-                _focusManager.layoutDirection = it
+                focusOwner.layoutDirection = it
             }
         }
     }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index 111d00e..7ce8bbb 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -1423,7 +1423,7 @@
             }
             AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS -> {
                 return if (node.unmergedConfig.getOrNull(SemanticsProperties.Focused) == true) {
-                    view.focusManager.clearFocus()
+                    view.focusOwner.clearFocus()
                     true
                 } else {
                     false
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt
index 51901fc..a06b7ea 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt
@@ -18,13 +18,7 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composer
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.focus.FocusEventModifier
-import androidx.compose.ui.focus.FocusEventModifierLocal
-import androidx.compose.ui.focus.FocusRequesterModifier
-import androidx.compose.ui.focus.FocusRequesterModifierLocal
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.InspectorValueInfo
 import androidx.compose.ui.platform.NoInspectorInfo
@@ -251,14 +245,7 @@
  */
 @Suppress("ModifierFactoryExtensionFunction")
 fun Composer.materialize(modifier: Modifier): Modifier {
-    if (modifier.all {
-            // onFocusEvent is implemented now with ModifierLocals and SideEffects, but
-            // FocusEventModifier needs to have composition to do the same. The following
-            // check for FocusEventModifier is only needed until the modifier is removed.
-            // The same is true for FocusRequesterModifier and focusTarget()
-            it !is ComposedModifier && it !is FocusEventModifier && it !is FocusRequesterModifier
-        }
-    ) {
+    if (modifier.all { it !is ComposedModifier }) {
         return modifier
     }
 
@@ -273,31 +260,12 @@
     val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
         acc.then(
             if (element is ComposedModifier) {
-                @kotlin.Suppress("UNCHECKED_CAST")
+                @Suppress("UNCHECKED_CAST")
                 val factory = element.factory as Modifier.(Composer, Int) -> Modifier
                 val composedMod = factory(Modifier, this, 0)
                 materialize(composedMod)
             } else {
-                // onFocusEvent is implemented now with ModifierLocals and SideEffects, but
-                // FocusEventModifier needs to have composition to do the same. The following
-                // check for FocusEventModifier is only needed until the modifier is removed.
-                var newElement: Modifier = element
-                if (element is FocusEventModifier) {
-                    @Suppress("UNCHECKED_CAST")
-                    val factory = WrapFocusEventModifier
-                        as (FocusEventModifier, Composer, Int) -> Modifier
-
-                    newElement = newElement.then(factory(element, this, 0))
-                }
-                // The same is true for FocusRequesterModifier and focusTarget()
-                if (element is FocusRequesterModifier) {
-                    @Suppress("UNCHECKED_CAST")
-                    val factory = WrapFocusRequesterModifier
-                        as (FocusRequesterModifier, Composer, Int) -> Modifier
-
-                    newElement = newElement.then(factory(element, this, 0))
-                }
-                newElement
+                element
             }
         )
     }
@@ -305,19 +273,3 @@
     endReplaceableGroup()
     return result
 }
-
-private val WrapFocusEventModifier: @Composable (FocusEventModifier) -> Modifier = { mod ->
-    val modifier = remember(mod) {
-        FocusEventModifierLocal(mod::onFocusEvent)
-    }
-    SideEffect {
-        modifier.notifyIfNoFocusModifiers()
-    }
-    modifier
-}
-
-private val WrapFocusRequesterModifier: @Composable (FocusRequesterModifier) -> Modifier = { mod ->
-    remember(mod) {
-        FocusRequesterModifierLocal(mod.focusRequester)
-    }
-}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/BeyondBoundsLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/BeyondBoundsLayout.kt
index 5e5191e..4f4e15f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/BeyondBoundsLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/BeyondBoundsLayout.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.focus
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.layout.BeyondBoundsLayout.BeyondBoundsScope
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Above
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.After
@@ -24,7 +25,8 @@
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Left
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Right
 
-internal fun <T> FocusModifier.searchBeyondBounds(
+@ExperimentalComposeUiApi
+internal fun <T> FocusTargetModifierNode.searchBeyondBounds(
     direction: FocusDirection,
     block: BeyondBoundsScope.() -> T?
 ): T? {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt
index 9440c6b..54ee8d3 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt
@@ -16,12 +16,9 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.node.modifierElementOf
 
 /**
  * Add this modifier to a component to observe focus state events. [onFocusChanged] is invoked
@@ -33,18 +30,29 @@
  * Note: If you want to be notified every time the internal focus state is written to (even if it
  * hasn't changed), use [onFocusEvent] instead.
  */
-fun Modifier.onFocusChanged(onFocusChanged: (FocusState) -> Unit): Modifier =
-    composed(
-        inspectorInfo = debugInspectorInfo {
+@Suppress("ModifierInspectorInfo")
+fun Modifier.onFocusChanged(onFocusChanged: (FocusState) -> Unit): Modifier = this.then(
+    @OptIn(ExperimentalComposeUiApi::class)
+    modifierElementOf(
+        key = onFocusChanged,
+        create = { FocusChangedModifierNode(onFocusChanged) },
+        update = { it. },
+        definitions = {
             name = "onFocusChanged"
             properties["onFocusChanged"] = onFocusChanged
         }
-    ) {
-        val focusState: MutableState<FocusState?> = remember { mutableStateOf(null) }
-        Modifier.onFocusEvent {
-            if (focusState.value != it) {
-                focusState.value = it
-                onFocusChanged(it)
-            }
+    )
+)
+
+@ExperimentalComposeUiApi
+private class FocusChangedModifierNode(
+    var onFocusChanged: (FocusState) -> Unit
+) : FocusEventModifierNode, Modifier.Node() {
+    var focusState: FocusState? = null
+    override fun onFocusEvent(focusState: FocusState) {
+        if (this.focusState != focusState) {
+            this.focusState = focusState
+            this.onFocusChanged.invoke(focusState)
         }
     }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusEventModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusEventModifier.kt
index ddfc229..4b175e1 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusEventModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusEventModifier.kt
@@ -16,29 +16,37 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.collection.MutableVector
-import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalProvider
-import androidx.compose.ui.modifier.ModifierLocalReadScope
-import androidx.compose.ui.modifier.ProvidableModifierLocal
-import androidx.compose.ui.modifier.modifierLocalOf
-import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.modifierElementOf
+import androidx.compose.ui.node.visitAncestors
+import androidx.compose.ui.node.visitChildren
+
+/**
+ * Implement this interface create a modifier node that can be used to observe focus state changes
+ * to a [FocusTargetModifierNode] down the hierarchy.
+ */
+@ExperimentalComposeUiApi
+interface FocusEventModifierNode : DelegatableNode {
+
+    /**
+     * A parent FocusEventNode is notified of [FocusState] changes to the [FocusTargetModifierNode]
+     * associated with this [FocusEventModifierNode].
+     */
+    fun onFocusEvent(focusState: FocusState)
+}
 
 /**
  * A [modifier][Modifier.Element] that can be used to observe focus state events.
  */
+@Deprecated("Use FocusEventModifierNode instead")
 @JvmDefaultWithCompatibility
 interface FocusEventModifier : Modifier.Element {
     /**
@@ -47,120 +55,62 @@
     fun onFocusEvent(focusState: FocusState)
 }
 
-internal val ModifierLocalFocusEvent = modifierLocalOf<FocusEventModifierLocal?> { null }
+@OptIn(ExperimentalComposeUiApi::class)
+internal class FocusEventModifierNodeImpl(
+    var onFocusEvent: (FocusState) -> Unit
+) : FocusEventModifierNode, Modifier.Node() {
 
-internal class FocusEventModifierLocal(
-    val onFocusEvent: (FocusState) -> Unit,
-) : ModifierLocalProvider<FocusEventModifierLocal?>, ModifierLocalConsumer {
-    // parent/children form three of FocusEventModifierLocals
-    private var parent: FocusEventModifierLocal? = null
-    private val children = mutableVectorOf<FocusEventModifierLocal>()
-
-    /**
-     * This is the list of modifiers that contribute to the focus event's state.
-     * When there are multiple, all FocusModifier states must be considered when notifying an event.
-     */
-    private val focusModifiers = mutableVectorOf<FocusModifier>()
-
-    override val key: ProvidableModifierLocal<FocusEventModifierLocal?>
-        get() = ModifierLocalFocusEvent
-    override val value: FocusEventModifierLocal
-        get() = this
-
-    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) = with(scope) {
-        val newParent = ModifierLocalFocusEvent.current
-        if (newParent != parent) {
-            parent?.let { parent ->
-                parent.children -= this@FocusEventModifierLocal
-                parent.removeFocusModifiers(focusModifiers)
-            }
-            parent = newParent
-            if (newParent != null) {
-                newParent.children += this@FocusEventModifierLocal
-                newParent.addFocusModifiers(focusModifiers)
-            }
-        }
-        parent = ModifierLocalFocusEvent.current
+    override fun onFocusEvent(focusState: FocusState) {
+        this.onFocusEvent.invoke(focusState)
     }
+}
 
-    fun addFocusModifier(focusModifier: FocusModifier) {
-        focusModifiers += focusModifier
-        parent?.addFocusModifier(focusModifier)
-    }
-
-    private fun addFocusModifiers(modifiers: MutableVector<FocusModifier>) {
-        focusModifiers.addAll(modifiers)
-        parent?.addFocusModifiers(modifiers)
-    }
-
-    fun removeFocusModifier(focusModifier: FocusModifier) {
-        focusModifiers -= focusModifier
-        parent?.removeFocusModifier(focusModifier)
-    }
-
-    private fun removeFocusModifiers(modifiers: MutableVector<FocusModifier>) {
-        focusModifiers.removeAll(modifiers)
-        parent?.removeFocusModifiers(modifiers)
-    }
-
-    fun propagateFocusEvent() {
-        val notifiedState = when (focusModifiers.size) {
-            0 -> Inactive
-            1 -> focusModifiers[0].focusState
-            else -> {
-                // We have multiple children, so we have to recalculate the focus state
-                var focusedChild: FocusModifier? = null
-                var allChildrenDisabled: Boolean? = null
-                focusModifiers.forEach {
-                    when (it.focusState) {
-                        Active,
-                        ActiveParent,
-                        Captured,
-                        DeactivatedParent -> {
-                            focusedChild = it
-                            allChildrenDisabled = false
-                        }
-                        Deactivated -> if (allChildrenDisabled == null) {
-                            allChildrenDisabled = true
-                        }
-                        Inactive -> allChildrenDisabled = false
-                    }
-                }
-
-                focusedChild?.focusState ?: if (allChildrenDisabled == true) {
-                    Deactivated
-                } else {
-                    Inactive
-                }
-            }
-        }
-        onFocusEvent(notifiedState)
-        parent?.propagateFocusEvent()
-    }
-
-    fun notifyIfNoFocusModifiers() {
-        if (focusModifiers.isEmpty()) {
-            onFocusEvent(FocusStateImpl.Inactive)
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun FocusEventModifierNode.getFocusState(): FocusState {
+    visitChildren(Nodes.FocusTarget) {
+        when (val focusState = it.focusStateImpl) {
+            // If we find a focused child, we use that child's state as the aggregated state.
+            Active, ActiveParent, Captured -> return focusState
+            // We use the Inactive state only if we don't have a focused child.
+            // ie. we ignore this child if another child provides aggregated state.
+            Inactive -> return@visitChildren
         }
     }
+    return Inactive
 }
 
 /**
  * Add this modifier to a component to observe focus state events.
  */
-fun Modifier.onFocusEvent(onFocusEvent: (FocusState) -> Unit): Modifier = composed(
-    debugInspectorInfo {
-        name = "onFocusEvent"
-        properties["onFocusEvent"] = onFocusEvent
-    }
-) {
-    val modifier = remember(onFocusEvent) {
-        FocusEventModifierLocal(>
-    }
+@Suppress("ModifierInspectorInfo") // b/251831790.
+fun Modifier.onFocusEvent(onFocusEvent: (FocusState) -> Unit): Modifier = this.then(
+    @OptIn(ExperimentalComposeUiApi::class)
+    modifierElementOf(
+        key = onFocusEvent,
+        create = { FocusEventModifierNodeImpl(onFocusEvent) },
+        update = { it. },
+        definitions = {
+            name = "onFocusEvent"
+            properties["onFocusEvent"] = onFocusEvent
+        }
+    )
+)
 
-    SideEffect {
-        modifier.notifyIfNoFocusModifiers()
-    }
+/**
+ * Sends a "Focus Event" up the hierarchy that asks all [FocusEventModifierNode]s to recompute their
+ * observed focus state.
+ *
+ * Make this public after [FocusTargetModifierNode] is made public.
+ */
+@ExperimentalComposeUiApi
+internal fun FocusTargetModifierNode.refreshFocusEventNodes() {
+    visitAncestors(Nodes.FocusEvent or Nodes.FocusTarget) {
+        // If we reach the previous focus target node, we have gone too far, as
+        //  this is applies to the another focus event.
+        if (it.isKind(Nodes.FocusTarget)) return
 
-    modifier
+        // TODO(251833873): Consider caching it.getFocusState().
+        check(it is FocusEventModifierNode)
+        it.onFocusEvent(it.getFocusState())
+    }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusInvalidationManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusInvalidationManager.kt
new file mode 100644
index 0000000..8421e1b
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusInvalidationManager.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.compose.ui.focus
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.focus.FocusStateImpl.Inactive
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.visitChildren
+
+/**
+ * The [FocusInvalidationManager] allows us to schedule focus related nodes for invalidation.
+ * These nodes are invalidated after onApplyChanges. It does this by registering an
+ * onApplyChangesListener when nodes are scheduled for invalidation.
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+internal class FocusInvalidationManager(
+    private val onRequestApplyChangesListener: (() -> Unit) -> Unit
+) {
+    private var focusTargetNodes = mutableSetOf<FocusTargetModifierNode>()
+    private var focusEventNodes = mutableSetOf<FocusEventModifierNode>()
+    private var focusPropertiesNodes = mutableSetOf<FocusPropertiesModifierNode>()
+
+    fun scheduleInvalidation(node: FocusTargetModifierNode) {
+        focusTargetNodes.scheduleInvalidation(node)
+    }
+
+    fun scheduleInvalidation(node: FocusEventModifierNode) {
+        focusEventNodes.scheduleInvalidation(node)
+    }
+
+    fun scheduleInvalidation(node: FocusPropertiesModifierNode) {
+        focusPropertiesNodes.scheduleInvalidation(node)
+    }
+
+    private fun <T> MutableSet<T>.scheduleInvalidation(node: T) {
+        // We don't schedule a node if it is already scheduled during this composition.
+        if (contains(node)) return
+
+        add(node)
+
+        // If this is the first node scheduled for invalidation,
+        // we set up a listener that runs after onApplyChanges.
+        if (focusTargetNodes.size + focusEventNodes.size + focusPropertiesNodes.size == 1) {
+            onRequestApplyChangesListener.invoke(invalidateNodes)
+        }
+    }
+
+    private val invalidateNodes: () -> Unit = {
+        // Process all the invalidated FocusProperties nodes.
+        focusPropertiesNodes.forEach {
+            it.visitChildren(Nodes.FocusTarget) { focusTarget ->
+                focusTargetNodes.add(focusTarget)
+            }
+        }
+        focusPropertiesNodes.clear()
+
+        // Process all the focus events nodes.
+        val focusTargetsWithInvalidatedFocusEvents = mutableSetOf<FocusTargetModifierNode>()
+        focusEventNodes.forEach { focusEventNode ->
+            // When focus nodes are removed, the corresponding focus events are scheduled for
+            // invalidation. If the focus event was also removed, we don't need to invalidate it.
+            if (!focusEventNode.node.isAttached) return@forEach
+
+            var requiresUpdate = true
+            var aggregatedNode = false
+            var focusTarget: FocusTargetModifierNode? = null
+            focusEventNode.visitChildren(Nodes.FocusTarget) {
+
+                // If there are multiple focus targets associated with this focus event node,
+                // we need to calculate the aggregated state.
+                if (focusTarget != null) {
+                    aggregatedNode = true
+                }
+
+                focusTarget = it
+
+                // If the associated focus node is already scheduled for invalidation, it will
+                // send an onFocusEvent if the invalidation causes a focus state change.
+                // However this onFocusEvent was invalidated, so we have to ensure that we call
+                // onFocusEvent even if the focus state didn't change.
+                if (focusTargetNodes.contains(it)) {
+                    requiresUpdate = false
+                    focusTargetsWithInvalidatedFocusEvents.add(it)
+                    return@visitChildren
+                }
+            }
+
+            if (requiresUpdate) {
+                focusEventNode.onFocusEvent(
+                    if (aggregatedNode) {
+                        focusEventNode.getFocusState()
+                    } else {
+                        focusTarget?.focusState ?: Inactive
+                    }
+                )
+            }
+        }
+        focusEventNodes.clear()
+
+        // Process all the focus target nodes.
+        focusTargetNodes.forEach {
+            // We don't need to invalidate the focus target if it was scheduled for invalidation
+            // earlier in the composition but was then removed.
+            if (!it.isAttached) return@forEach
+
+            val preInvalidationState = it.focusState
+            it.invalidateFocus()
+            if (preInvalidationState != it.focusState ||
+                focusTargetsWithInvalidatedFocusEvents.contains(it)) {
+                it.refreshFocusEventNodes()
+            }
+        }
+        focusTargetNodes.clear()
+        focusTargetsWithInvalidatedFocusEvents.clear()
+
+        check(focusPropertiesNodes.isEmpty())
+        check(focusEventNodes.isEmpty())
+        check(focusTargetNodes.isEmpty())
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
index 9bc7c0b..f7574c4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
@@ -25,11 +25,22 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyInputModifierNode
+import androidx.compose.ui.input.rotary.RotaryScrollEvent
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.NodeKind
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.ancestors
+import androidx.compose.ui.node.modifierElementOf
+import androidx.compose.ui.node.nearestAncestor
+import androidx.compose.ui.node.visitLocalChildren
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachReversed
 
 @JvmDefaultWithCompatibility
 interface FocusManager {
@@ -60,45 +71,54 @@
 /**
  * The focus manager is used by different [Owner][androidx.compose.ui.node.Owner] implementations
  * to control focus.
- *
- * @param focusModifier The modifier that will be used as the root focus modifier.
  */
-internal class FocusManagerImpl(
-    private val focusModifier: FocusModifier = FocusModifier(Inactive)
-) : FocusManager {
+internal class FocusOwnerImpl(onRequestApplyChangesListener: (() -> Unit) -> Unit) : FocusOwner {
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    internal var rootFocusNode = FocusTargetModifierNode()
+
+    private val focusInvalidationManager = FocusInvalidationManager(onRequestApplyChangesListener)
 
     /**
      * A [Modifier] that can be added to the [Owners][androidx.compose.ui.node.Owner] modifier
      * list that contains the modifiers required by the focus system. (Eg, a root focus modifier).
      */
     // TODO(b/168831247): return an empty Modifier when there are no focusable children.
-    val modifier: Modifier = Modifier.focusTarget(focusModifier)
+    @Suppress("ModifierInspectorInfo") // b/251831790.
+    @OptIn(ExperimentalComposeUiApi::class)
+    override val modifier: Modifier = modifierElementOf(
+        create = { rootFocusNode },
+        definitions = { name = "RootFocusTarget" }
+    )
 
-    lateinit var layoutDirection: LayoutDirection
+    override lateinit var layoutDirection: LayoutDirection
 
     /**
      * The [Owner][androidx.compose.ui.node.Owner] calls this function when it gains focus. This
-     * informs the [focus manager][FocusManagerImpl] that the
+     * informs the [focus manager][FocusOwnerImpl] that the
      * [Owner][androidx.compose.ui.node.Owner] gained focus, and that it should propagate this
      * focus to one of the focus modifiers in the component hierarchy.
      */
-    fun takeFocus() {
+    override fun takeFocus() {
         // If the focus state is not Inactive, it indicates that the focus state is already
         // set (possibly by dispatchWindowFocusChanged). So we don't update the state.
-        if (focusModifier.focusState == Inactive) {
-            focusModifier.focusState = Active
+        @OptIn(ExperimentalComposeUiApi::class)
+        if (rootFocusNode.focusStateImpl == Inactive) {
+            rootFocusNode.focusStateImpl = Active
             // TODO(b/152535715): propagate focus to children based on child focusability.
+            //  moveFocus(FocusDirection.Enter)
         }
     }
 
     /**
      * The [Owner][androidx.compose.ui.node.Owner] calls this function when it loses focus. This
-     * informs the [focus manager][FocusManagerImpl] that the
+     * informs the [focus manager][FocusOwnerImpl] that the
      * [Owner][androidx.compose.ui.node.Owner] lost focus, and that it should clear focus from
      * all the focus modifiers in the component hierarchy.
      */
-    fun releaseFocus() {
-        focusModifier.clearFocus(forcedClear = true)
+    override fun releaseFocus() {
+        @OptIn(ExperimentalComposeUiApi::class)
+        rootFocusNode.clearFocus(forced = true, refreshFocusEvents = true)
     }
 
     /**
@@ -111,14 +131,18 @@
      * component.
      */
     override fun clearFocus(force: Boolean) {
+        clearFocus(force, refreshFocusEvents = true)
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    override fun clearFocus(force: Boolean, refreshFocusEvents: Boolean) {
         // If this hierarchy had focus before clearing it, it indicates that the host view has
         // focus. So after clearing focus within the compose hierarchy, we should restore focus to
         // the root focus modifier to maintain consistency with the host view.
-        val rootInitialState = focusModifier.focusState
-        if (focusModifier.clearFocus(force)) {
-            focusModifier.focusState = when (rootInitialState) {
+        val rootInitialState = rootFocusNode.focusStateImpl
+        if (rootFocusNode.clearFocus(force, refreshFocusEvents)) {
+            rootFocusNode.focusStateImpl = when (rootInitialState) {
                 Active, ActiveParent, Captured -> Active
-                Deactivated, DeactivatedParent -> Deactivated
                 Inactive -> Inactive
             }
         }
@@ -129,22 +153,23 @@
      *
      * @return true if focus was moved successfully. false if the focused item is unchanged.
      */
+    @OptIn(ExperimentalComposeUiApi::class)
     override fun moveFocus(focusDirection: FocusDirection): Boolean {
 
         // If there is no active node in this sub-hierarchy, we can't move focus.
-        val source = focusModifier.findActiveFocusNode() ?: return false
+        val source = rootFocusNode.findActiveFocusNode() ?: return false
 
         // Check if a custom focus traversal order is specified.
-        val nextFocusRequester = source.customFocusSearch(focusDirection, layoutDirection)
-
-        return when (nextFocusRequester) {
+        return when (val next = source.customFocusSearch(focusDirection, layoutDirection)) {
             @OptIn(ExperimentalComposeUiApi::class)
             Cancel -> false
             Default -> {
                 val foundNextItem =
-                    focusModifier.focusSearch(focusDirection, layoutDirection) { destination ->
+                    rootFocusNode.focusSearch(focusDirection, layoutDirection) { destination ->
                         if (destination == source) return@focusSearch false
-                        checkNotNull(destination.parent) { "Focus search landed at the root." }
+                        checkNotNull(destination.nearestAncestor(Nodes.FocusTarget)) {
+                            "Focus search landed at the root."
+                        }
                         // If we found a potential next item, move focus to it.
                         destination.requestFocus()
                         true
@@ -154,33 +179,98 @@
             }
             else -> {
                 // TODO(b/175899786): We ideally need to check if the nextFocusRequester points to
-                //  something that is visible and focusable in the current mode (Touch/Non-Touch mode).
-                nextFocusRequester.requestFocus()
+                //  something that is visible and focusable in the current mode (Touch/Non-Touch
+                //  mode).
+                next.requestFocus()
                 true
             }
         }
     }
 
     /**
-     * Runs the focus properties block for all [focusProperties] modifiers to fetch updated
-     * [FocusProperties].
-     *
-     * The [focusProperties] block is run automatically whenever the properties change, and you
-     * rarely need to invoke this function manually. However, if you have a situation where you want
-     * to change a property, and need to see the change in the current snapshot, use this API.
+     * Dispatches a key event through the compose hierarchy.
      */
-    fun fetchUpdatedFocusProperties() {
-        focusModifier.updateProperties()
+    @OptIn(ExperimentalComposeUiApi::class)
+    override fun dispatchKeyEvent(keyEvent: KeyEvent): Boolean {
+        val activeFocusTarget = rootFocusNode.findActiveFocusNode()
+        checkNotNull(activeFocusTarget) {
+            "Event can't be processed because we do not have an active focus target."
+        }
+        val focusedKeyInputNode = activeFocusTarget.lastLocalKeyInputNode()
+            ?: activeFocusTarget.nearestAncestor(Nodes.KeyInput)
+
+        focusedKeyInputNode?.traverseAncestors(
+            type = Nodes.KeyInput,
+             if (it.onPreKeyEvent(keyEvent)) return true },
+             if (it.onKeyEvent(keyEvent)) return true }
+        )
+
+        return false
     }
 
     /**
-     * Searches for the currently focused item.
-     *
-     * @return the currently focused item.
+     * Dispatches a rotary scroll event through the compose hierarchy.
      */
-    @Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-    internal fun getActiveFocusModifier(): FocusModifier? {
-        return focusModifier.findActiveItem()
+    @OptIn(ExperimentalComposeUiApi::class)
+    override fun dispatchRotaryEvent(event: RotaryScrollEvent): Boolean {
+        val focusedRotaryInputNode = rootFocusNode.findActiveFocusNode()
+            ?.nearestAncestor(Nodes.RotaryInput)
+
+        focusedRotaryInputNode?.traverseAncestors(
+            type = Nodes.RotaryInput,
+             if (it.onPreRotaryScrollEvent(event)) return true },
+             if (it.onRotaryScrollEvent(event)) return true }
+        )
+
+        return false
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    override fun scheduleInvalidation(node: FocusTargetModifierNode) {
+        focusInvalidationManager.scheduleInvalidation(node)
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    override fun scheduleInvalidation(node: FocusEventModifierNode) {
+        focusInvalidationManager.scheduleInvalidation(node)
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    override fun scheduleInvalidation(node: FocusPropertiesModifierNode) {
+        focusInvalidationManager.scheduleInvalidation(node)
+    }
+
+    @ExperimentalComposeUiApi
+    private inline fun <reified T : DelegatableNode> T.traverseAncestors(
+        type: NodeKind<T>,
+        onPreVisit: (T) -> Unit,
+        onVisit: (T) -> Unit
+    ) {
+        val ancestors = ancestors(type)
+        ancestors?.fastForEachReversed(onPreVisit)
+        onPreVisit(this)
+        onVisit(this)
+        ancestors?.fastForEach(onVisit)
+    }
+
+    /**
+     * Searches for the currently focused item, and returns its coordinates as a rect.
+     */
+    override fun getFocusRect(): Rect? {
+        @OptIn(ExperimentalComposeUiApi::class)
+        return rootFocusNode.findActiveFocusNode()?.focusRect()
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    private fun DelegatableNode.lastLocalKeyInputNode(): KeyInputModifierNode? {
+        var focusedKeyInputNode: KeyInputModifierNode? = null
+        visitLocalChildren(Nodes.FocusTarget or Nodes.KeyInput) { modifierNode ->
+            if (modifierNode.isKind(Nodes.FocusTarget)) return focusedKeyInputNode
+
+            check(modifierNode is KeyInputModifierNode)
+            focusedKeyInputNode = modifierNode
+        }
+        return focusedKeyInputNode
     }
 
     // TODO(b/144116848): This is a hack to make Next/Previous wrap around. This must be
@@ -188,14 +278,16 @@
     //  will then pass focus to other views, and ultimately return back to this compose view.
     private fun wrapAroundFocus(focusDirection: FocusDirection): Boolean {
         // Wrap is not supported when this sub-hierarchy doesn't have focus.
-        if (!focusModifier.focusState.hasFocus || focusModifier.focusState.isFocused) return false
+        @OptIn(ExperimentalComposeUiApi::class)
+        if (!rootFocusNode.focusState.hasFocus || rootFocusNode.focusState.isFocused) return false
 
         // Next and Previous wraps around.
         when (focusDirection) {
             Next, Previous -> {
                 // Clear Focus to send focus the root node.
                 clearFocus(force = false)
-                if (!focusModifier.focusState.isFocused) return false
+                @OptIn(ExperimentalComposeUiApi::class)
+                if (!rootFocusNode.focusState.isFocused) return false
 
                 // Wrap around by calling moveFocus after the root gains focus.
                 return moveFocus(focusDirection)
@@ -205,25 +297,3 @@
         }
     }
 }
-
-private fun FocusModifier.updateProperties() {
-    // Update the focus node with the current focus properties.
-    refreshFocusProperties()
-
-    // Update the focus properties for all children.
-    children.forEach { it.updateProperties() }
-}
-
-/**
- * Find the focus modifier in this sub-hierarchy that is currently focused.
- */
-@Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-private fun FocusModifier.findActiveItem(): FocusModifier? {
-    return when (focusState) {
-        Active, Captured -> this
-        ActiveParent, DeactivatedParent -> {
-            focusedChild?.findActiveItem() ?: error("no child")
-        }
-        Deactivated, Inactive -> null
-    }
-}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
index 1a71a0b9e..cf58238 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
@@ -16,166 +16,108 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
-import androidx.compose.ui.input.focus.FocusAwareInputModifier
-import androidx.compose.ui.input.key.KeyInputModifier
-import androidx.compose.ui.input.key.ModifierLocalKeyInput
-import androidx.compose.ui.input.rotary.ModifierLocalRotaryScrollParent
-import androidx.compose.ui.input.rotary.RotaryScrollEvent
 import androidx.compose.ui.layout.BeyondBoundsLayout
-import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
-import androidx.compose.ui.layout.OnPlacedModifier
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalProvider
-import androidx.compose.ui.modifier.ModifierLocalReadScope
-import androidx.compose.ui.modifier.ProvidableModifierLocal
-import androidx.compose.ui.modifier.modifierLocalOf
-import androidx.compose.ui.node.NodeCoordinator
-import androidx.compose.ui.node.OwnerScope
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.InspectorValueInfo
-import androidx.compose.ui.platform.NoInspectorInfo
-import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.ObserverNode
+import androidx.compose.ui.node.modifierElementOf
+import androidx.compose.ui.node.observeReads
+import androidx.compose.ui.node.requireOwner
+import androidx.compose.ui.node.visitAncestors
 
 /**
- * Used to build a tree of [FocusModifier] elements. This contains the parent.
+ * This modifier node can be used to create a modifier that makes a component focusable.
+ * Use a different instance of [FocusTargetModifierNode] for each focusable component.
  */
-internal val ModifierLocalParentFocusModifier = modifierLocalOf<FocusModifier?> { null }
-
-/**
- * A [Modifier.Element] that wraps makes the modifiers on the right into a Focusable. Use a
- * different instance of [FocusModifier] for each focusable component.
- */
-internal class FocusModifier(
-    initialFocus: FocusStateImpl,
-    // TODO(b/172265016): Make this a required parameter and remove the default value.
-    //  Set this value in AndroidComposeView, and other places where we create a focus modifier
-    //  using this internal constructor.
-    inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo
-) : ModifierLocalConsumer,
-    ModifierLocalProvider<FocusModifier?>,
-    OwnerScope,
-    OnPlacedModifier,
-    InspectorValueInfo(inspectorInfo) {
-    // TODO(b/188684110): Move focusState and focusedChild to ModifiedFocusNode and make this
-    //  modifier stateless.
-    var parent: FocusModifier? = null
-    val children = mutableVectorOf<FocusModifier>()
-    var focusState: FocusStateImpl = initialFocus
-        set(value) {
-            field = value
-            sendOnFocusEvent()
-        }
-    var focusedChild: FocusModifier? = null
-    var focusEventListener: FocusEventModifierLocal? = null
-    @OptIn(ExperimentalComposeUiApi::class)
-    private var rotaryScrollParent: FocusAwareInputModifier<RotaryScrollEvent>? = null
-    lateinit var modifierLocalReadScope: ModifierLocalReadScope
-    var beyondBoundsLayoutParent: BeyondBoundsLayout? = null
-    var focusPropertiesModifier: FocusPropertiesModifier? = null
-    val focusProperties: FocusProperties = FocusPropertiesImpl()
-    var focusRequester: FocusRequesterModifierLocal? = null
-    var coordinator: NodeCoordinator? = null
-    var focusRequestedOnPlaced = false
-
+@ExperimentalComposeUiApi
+class FocusTargetModifierNode : ObserverNode, ModifierLocalNode, Modifier.Node() {
     /**
-     * The KeyInputModifier that this FocusModifier comes after.
+     * The [FocusState] associated with this [FocusTargetModifierNode].
      */
-    var keyInputModifier: KeyInputModifier? = null
-        private set
+    val focusState: FocusState
+        get() = focusStateImpl
 
-    /**
-     * All KeyInputModifiers that read this [FocusModifier] in the
-     * [ModifierLocalParentFocusModifier].
-     */
-    val keyInputChildren = mutableVectorOf<KeyInputModifier>()
+    internal var focusStateImpl = Inactive
+    internal val beyondBoundsLayoutParent: BeyondBoundsLayout?
+        get() = ModifierLocalBeyondBoundsLayout.current
 
-    // Reading the FocusProperties ModifierLocal.
-    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
-        modifierLocalReadScope = scope
+    override fun onObservedReadsChanged() {
+        val previousFocusState = focusState
+        invalidateFocus()
+        if (previousFocusState != focusState) refreshFocusEventNodes()
+    }
 
-        with(scope) {
-            parent = ModifierLocalParentFocusModifier.current.also { newParent ->
-                if (newParent != parent) {
-                    if (newParent == null) {
-                        when (focusState) {
-                            Active, Captured -> coordinator?.layoutNode?.owner
-                                ?.focusManager?.clearFocus(force = true)
-                            ActiveParent, DeactivatedParent, Deactivated, Inactive -> {
-                                // do nothing.
-                            }
-                        }
-                    }
-                    parent?.children?.remove(this@FocusModifier)
-                    newParent?.children?.add(this@FocusModifier)
-                }
-            }
-            focusEventListener = ModifierLocalFocusEvent.current.also { newFocusEventListener ->
-                if (newFocusEventListener != focusEventListener) {
-                    focusEventListener?.removeFocusModifier(this@FocusModifier)
-                    newFocusEventListener?.addFocusModifier(this@FocusModifier)
-                }
-            }
-            focusRequester = ModifierLocalFocusRequester.current.also { newFocusRequester ->
-                if (newFocusRequester != focusRequester) {
-                    focusRequester?.removeFocusModifier(this@FocusModifier)
-                    newFocusRequester?.addFocusModifier(this@FocusModifier)
-                }
-            }
-            @OptIn(ExperimentalComposeUiApi::class)
-            rotaryScrollParent = ModifierLocalRotaryScrollParent.current
-            beyondBoundsLayoutParent = ModifierLocalBeyondBoundsLayout.current
-            keyInputModifier = ModifierLocalKeyInput.current
-            focusPropertiesModifier = ModifierLocalFocusProperties.current
+    internal fun onRemoved() {
+        when (focusState) {
+            // Clear focus from the current FocusTarget.
+            // This currently clears focus from the entire hierarchy, but we can change the
+            // implementation so that focus is sent to the immediate focus parent.
+            Active, Captured -> requireOwner().focusOwner.clearFocus(force = true)
 
-            // Update the focus node with the current focus properties.
-            refreshFocusProperties()
+            ActiveParent, Inactive -> scheduleInvalidationForFocusEvents()
         }
     }
 
+    internal fun invalidateFocus() {
+        when (focusState) {
+            // Clear focus from the current FocusTarget.
+            // This currently clears focus from the entire hierarchy, but we can change the
+            // implementation so that focus is sent to the immediate focus parent.
+            Active, Captured -> {
+                lateinit var focusProperties: FocusProperties
+                observeReads {
+                    focusProperties = fetchFocusProperties()
+                }
+                if (!focusProperties.canFocus) {
+                    requireOwner().focusOwner.clearFocus(force = true)
+                }
+            }
+
+            ActiveParent, Inactive -> {}
+        }
+    }
+
+    /**
+     * Visits parent [FocusPropertiesModifierNode]s and runs
+     * [FocusPropertiesModifierNode.modifyFocusProperties] on each parent.
+     * This effectively collects an aggregated focus state.
+     */
     @ExperimentalComposeUiApi
-    fun propagateRotaryEvent(event: RotaryScrollEvent): Boolean {
-        return rotaryScrollParent?.propagateFocusAwareEvent(event) ?: false
+    internal fun fetchFocusProperties(): FocusProperties {
+        val properties = FocusPropertiesImpl()
+        visitAncestors(Nodes.FocusProperties or Nodes.FocusTarget) {
+            // If we reach the previous default focus properties node, we have gone too far, as
+            //  this is applies to the parent focus modifier.
+            if (it.isKind(Nodes.FocusTarget)) return properties
+
+            // Parent can override any values set by this
+            check(it is FocusPropertiesModifierNode)
+            it.modifyFocusProperties(properties)
+        }
+        return properties
     }
 
-    // For the RefreshFocusProperties observation. This shouldn't change on the root, so
-    // we don't need to keep lambdas around for the root element.
-    override val isValid: Boolean
-        get() = parent != null
+    internal fun scheduleInvalidationForFocusEvents() {
+        visitAncestors(Nodes.FocusEvent or Nodes.FocusTarget) {
+            if (it.isKind(Nodes.FocusTarget)) return@visitAncestors
 
-    companion object {
-        val RefreshFocusProperties: (FocusModifier) -> Unit = { focusModifier ->
-            focusModifier.refreshFocusProperties()
+            check(it is FocusEventModifierNode)
+            requireOwner().focusOwner.scheduleInvalidation(it)
         }
     }
 
-    override val key: ProvidableModifierLocal<FocusModifier?>
-        get() = ModifierLocalParentFocusModifier
-    override val value: FocusModifier
-        get() = this
-
-    override fun onPlaced(coordinates: LayoutCoordinates) {
-        val wasNull = coordinator == null
-        coordinator = coordinates as NodeCoordinator
-        if (wasNull) {
-            refreshFocusProperties()
-        }
-        if (focusRequestedOnPlaced) {
-            focusRequestedOnPlaced = false
-            requestFocus()
-        }
+    internal companion object {
+        internal val FocusTargetModifierElement = modifierElementOf(
+            create = { FocusTargetModifierNode() },
+            definitions = { name = "focusTarget" }
+        )
     }
 }
 
@@ -192,13 +134,8 @@
  *
  * @sample androidx.compose.ui.samples.FocusableSampleUsingLowerLevelFocusTarget
  */
-fun Modifier.focusTarget(): Modifier = composed(debugInspectorInfo { name = "focusTarget" }) {
-    val focusModifier = remember { FocusModifier(Inactive) }
-    SideEffect {
-        focusModifier.sendOnFocusEvent()
-    }
-    focusTarget(focusModifier)
-}
+@OptIn(ExperimentalComposeUiApi::class)
+fun Modifier.focusTarget(): Modifier = this then FocusTargetModifierNode.FocusTargetModifierElement
 
 /**
  * Add this modifier to a component to make it focusable.
@@ -207,76 +144,4 @@
     "Replaced by focusTarget",
     ReplaceWith("focusTarget()", "androidx.compose.ui.focus.focusTarget")
 )
-fun Modifier.focusModifier(): Modifier = composed(debugInspectorInfo { name = "focusModifier" }) {
-    val focusModifier = remember { FocusModifier(Inactive) }
-    SideEffect {
-        focusModifier.sendOnFocusEvent()
-    }
-    focusTarget(focusModifier)
-}
-
-/**
- * A helper function that allows you to pass in an instance of FocusModifier.
- * This is only used internally, to set the root focus modifier or in tests where we need to set an
- * initial focus state or inspect the focus modifier state after running some operation.
- */
-internal fun Modifier.focusTarget(focusModifier: FocusModifier): Modifier {
-    return this.then(focusModifier).then(ResetFocusModifierLocals)
-}
-
-/**
- * The Focus Modifier reads the state of some Modifier Locals that are set by the parents. Consider
- * the following example:
- *
- *     Box(
- *         Modifier
- *             .focusRequester(item1)
- *             .onFocusChanged { ... }
- *             .focusProperties {
- *                 canFocus = false
- *                 next = item2
- *             }
- *             .focusTarget()          // focusModifier1
- *     ) {
- *         Box(
- *             Modifier.focusTarget()  // focusModifier2
- *         )
- *     }
- *
- * Here, the focusRequester, onFocusChanged, and focusProperties modifiers provide
- * modifier local values that are intended for focusModifier1.
- *
- * We don't want these modifier locals to be read by focusModifier2.
- *
- * Add this modifier after every FocusModifier to reset all the focus related modifier locals, so
- * that focus modifiers further down the tree do not read these values.
- */
-internal val ResetFocusModifierLocals: Modifier = Modifier
-    // Reset the FocusProperties modifier local.
-    .then(
-        object : ModifierLocalProvider<FocusPropertiesModifier?> {
-            override val key: ProvidableModifierLocal<FocusPropertiesModifier?>
-                get() = ModifierLocalFocusProperties
-            override val value: FocusPropertiesModifier?
-                get() = null
-        }
-
-    )
-    // Update the FocusEvent listener modifier local value to null.
-    .then(
-        object : ModifierLocalProvider<FocusEventModifierLocal?> {
-            override val key: ProvidableModifierLocal<FocusEventModifierLocal?>
-                get() = ModifierLocalFocusEvent
-            override val value: FocusEventModifierLocal?
-                get() = null
-        }
-    )
-    // Update the FocusRequesters modifier local value to null.
-    .then(
-        object : ModifierLocalProvider<FocusRequesterModifierLocal?> {
-            override val key: ProvidableModifierLocal<FocusRequesterModifierLocal?>
-                get() = ModifierLocalFocusRequester
-            override val value: FocusRequesterModifierLocal?
-                get() = null
-        }
-    )
\ No newline at end of file
+fun Modifier.focusModifier(): Modifier = focusTarget()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt
index f011da1..d82bd8e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt
@@ -212,10 +212,12 @@
  * focus search.
  * @param layoutDirection the current system [LayoutDirection].
  */
-internal fun FocusModifier.customFocusSearch(
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun FocusTargetModifierNode.customFocusSearch(
     focusDirection: FocusDirection,
     layoutDirection: LayoutDirection
 ): FocusRequester {
+    val focusProperties = fetchFocusProperties()
     return when (focusDirection) {
         Next -> focusProperties.next
         Previous -> focusProperties.previous
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt
new file mode 100644
index 0000000..7393f77
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.compose.ui.focus
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.rotary.RotaryScrollEvent
+import androidx.compose.ui.unit.LayoutDirection
+
+/**
+ * The focus owner provides some internal APIs that are not exposed by focus manager.
+ */
+internal interface FocusOwner : FocusManager {
+
+    /**
+     * A [Modifier] that can be added to the [Owners][androidx.compose.ui.node.Owner] modifier
+     * list that contains the modifiers required by the focus system. (Eg, a root focus modifier).
+     */
+    val modifier: Modifier
+
+    /**
+     * The owner sets the layoutDirection that is then used during focus search.
+     */
+    var layoutDirection: LayoutDirection
+
+    /**
+     * The [Owner][androidx.compose.ui.node.Owner] calls this function when it gains focus. This
+     * informs the [focus manager][FocusOwnerImpl] that the
+     * [Owner][androidx.compose.ui.node.Owner] gained focus, and that it should propagate this
+     * focus to one of the focus modifiers in the component hierarchy.
+     */
+    fun takeFocus()
+
+    /**
+     * The [Owner][androidx.compose.ui.node.Owner] calls this function when it loses focus. This
+     * informs the [focus manager][FocusOwnerImpl] that the
+     * [Owner][androidx.compose.ui.node.Owner] lost focus, and that it should clear focus from
+     * all the focus modifiers in the component hierarchy.
+     */
+    fun releaseFocus()
+
+    /**
+     * Call this function to set the focus to the root focus modifier.
+     *
+     * @param force: Whether we should forcefully clear focus regardless of whether we have
+     * any components that have captured focus.
+     *
+     * @param refreshFocusEvents: Whether we should send an event up the hierarchy to update
+     * the associated onFocusEvent nodes.
+     *
+     * This could be used to clear focus when a user clicks on empty space outside a focusable
+     * component.
+     */
+    fun clearFocus(force: Boolean, refreshFocusEvents: Boolean)
+
+    /**
+     * Searches for the currently focused item, and returns its coordinates as a rect.
+     */
+    fun getFocusRect(): Rect?
+
+    /**
+     * Dispatches a key event through the compose hierarchy.
+     */
+    fun dispatchKeyEvent(keyEvent: KeyEvent): Boolean
+
+    /**
+     * Dispatches a rotary scroll event through the compose hierarchy.
+     */
+    @OptIn(ExperimentalComposeUiApi::class)
+    fun dispatchRotaryEvent(event: RotaryScrollEvent): Boolean
+
+    /**
+     * Schedule a FocusTarget node to be invalidated after onApplyChanges.
+     */
+    @OptIn(ExperimentalComposeUiApi::class)
+    fun scheduleInvalidation(node: FocusTargetModifierNode)
+
+    /**
+     * Schedule a FocusEvent node to be invalidated after onApplyChanges.
+     */
+    @OptIn(ExperimentalComposeUiApi::class)
+    fun scheduleInvalidation(node: FocusEventModifierNode)
+
+    /**
+     * Schedule a FocusProperties node to be invalidated after onApplyChanges.
+     */
+    @OptIn(ExperimentalComposeUiApi::class)
+    fun scheduleInvalidation(node: FocusPropertiesModifierNode)
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusProperties.kt
index 54178d3..5dd4a6e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusProperties.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusProperties.kt
@@ -16,31 +16,32 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalProvider
-import androidx.compose.ui.modifier.ModifierLocalReadScope
-import androidx.compose.ui.modifier.modifierLocalOf
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.InspectorValueInfo
-import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.modifierElementOf
+import androidx.compose.ui.node.requireOwner
+import androidx.compose.ui.node.visitChildren
 
 /**
- * A Modifier local that stores [FocusProperties] for a sub-hierarchy.
- *
- * @see [focusProperties]
+ * Implement this interface create a modifier node that can be used to modify the focus properties
+ * of the associated [FocusTargetModifierNode].
  */
-internal val ModifierLocalFocusProperties =
-    modifierLocalOf<FocusPropertiesModifier?> { null }
+@ExperimentalComposeUiApi
+interface FocusPropertiesModifierNode : DelegatableNode {
+    /**
+     * A parent can modify the focus properties associated with the nearest
+     * [FocusTargetModifierNode] child node. If a [FocusTargetModifierNode] has multiple parent
+     * [FocusPropertiesModifierNode]s, properties set by a parent higher up in the hierarchy
+     * overwrite properties set by those that are lower in the hierarchy.
+     */
+    fun modifyFocusProperties(focusProperties: FocusProperties)
+}
 
 /**
- * Properties that are applied to [focusTarget]s that can read the [ModifierLocalFocusProperties]
- * Modifier Local.
+ * Properties that are applied to [focusTarget] that is the first child of the
+ * [FocusPropertiesModifierNode] that sets these properties.
  *
  * @see [focusProperties]
  */
@@ -178,53 +179,30 @@
  *
  * @sample androidx.compose.ui.samples.FocusPropertiesSample
  */
+@Suppress("ModifierInspectorInfo") // b/251831790.
 fun Modifier.focusProperties(scope: FocusProperties.() -> Unit): Modifier = this.then(
-    FocusPropertiesModifier(
-        focusPropertiesScope = scope,
-        inspectorInfo = debugInspectorInfo {
+    @OptIn(ExperimentalComposeUiApi::class)
+    modifierElementOf(
+        key = scope,
+        create = { FocusPropertiesModifierNodeImpl(scope) },
+        update = { it.focusPropertiesScope = scope },
+        definitions = {
             name = "focusProperties"
             properties["scope"] = scope
         }
     )
 )
 
-@Stable
-internal class FocusPropertiesModifier(
-    val focusPropertiesScope: FocusProperties.() -> Unit,
-    inspectorInfo: InspectorInfo.() -> Unit
-) : ModifierLocalConsumer,
-    ModifierLocalProvider<FocusPropertiesModifier?>,
-    InspectorValueInfo(inspectorInfo) {
+@OptIn(ExperimentalComposeUiApi::class)
+internal class FocusPropertiesModifierNodeImpl(
+    internal var focusPropertiesScope: FocusProperties.() -> Unit,
+) : FocusPropertiesModifierNode, Modifier.Node() {
 
-    private var parent: FocusPropertiesModifier? by mutableStateOf(null)
-
-    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
-        parent = scope.run { ModifierLocalFocusProperties.current }
-    }
-
-    override val key = ModifierLocalFocusProperties
-
-    override val value: FocusPropertiesModifier
-        get() = this
-
-    override fun equals(other: Any?) =
-        other is FocusPropertiesModifier && focusPropertiesScope == other.focusPropertiesScope
-
-    override fun hashCode() = focusPropertiesScope.hashCode()
-
-    fun calculateProperties(focusProperties: FocusProperties) {
-        // Populate with the specified focus properties.
+    override fun modifyFocusProperties(focusProperties: FocusProperties) {
         focusProperties.apply(focusPropertiesScope)
-
-        // Parent can override any values set by this
-        parent?.calculateProperties(focusProperties)
     }
 }
 
-internal fun FocusModifier.setUpdatedProperties(properties: FocusProperties) {
-    if (properties.canFocus) activateNode() else deactivateNode()
-}
-
 internal class FocusPropertiesImpl : FocusProperties {
     override var canFocus: Boolean = true
     override var next: FocusRequester = FocusRequester.Default
@@ -241,29 +219,11 @@
     override var exit: (FocusDirection) -> FocusRequester = { FocusRequester.Default }
 }
 
-internal fun FocusProperties.clear() {
-    canFocus = true
-    next = FocusRequester.Default
-    previous = FocusRequester.Default
-    up = FocusRequester.Default
-    down = FocusRequester.Default
-    left = FocusRequester.Default
-    right = FocusRequester.Default
-    start = FocusRequester.Default
-    end = FocusRequester.Default
-    @OptIn(ExperimentalComposeUiApi::class)
-    enter = { FocusRequester.Default }
-    @OptIn(ExperimentalComposeUiApi::class)
-    exit = { FocusRequester.Default }
-}
-
-internal fun FocusModifier.refreshFocusProperties() {
-    val coordinator = coordinator ?: return
-    focusProperties.clear()
-    coordinator.layoutNode.owner?.snapshotObserver?.observeReads(this,
-        FocusModifier.RefreshFocusProperties
-    ) {
-        focusPropertiesModifier?.calculateProperties(focusProperties)
+@ExperimentalComposeUiApi
+internal fun FocusPropertiesModifierNode.scheduleInvalidationOfAssociatedFocusTargets() {
+    visitChildren(Nodes.FocusTarget) {
+        // Schedule invalidation for the focus target,
+        // which will cause it to recalculate focus properties.
+        requireOwner().focusOwner.scheduleInvalidation(it)
     }
-    setUpdatedProperties(focusProperties)
-}
\ No newline at end of file
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
index 3d819cd..2c66b55 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
@@ -19,6 +19,8 @@
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.visitChildren
 
 private const val focusRequesterNotInitialized = """
    FocusRequester is not initialized. Here are some possible fixes:
@@ -40,8 +42,8 @@
  */
 class FocusRequester {
 
-    internal val focusRequesterModifierLocals: MutableVector<FocusRequesterModifierLocal> =
-        mutableVectorOf()
+    @OptIn(ExperimentalComposeUiApi::class)
+    internal val focusRequesterNodes: MutableVector<FocusRequesterModifierNode> = mutableVectorOf()
 
     /**
      * Use this function to request focus. If the system grants focus to a component associated
@@ -51,12 +53,12 @@
      * @sample androidx.compose.ui.samples.RequestFocusSample
      */
     fun requestFocus() {
-        check(focusRequesterModifierLocals.isNotEmpty()) { focusRequesterNotInitialized }
-        performRequestFocus {
-            it.requestFocus()
-            // TODO(b/245755256): Make focusModifier.requestFocus() return a Boolean.
-            true
-        }
+        @OptIn(ExperimentalComposeUiApi::class)
+        check(focusRequesterNodes.isNotEmpty()) { focusRequesterNotInitialized }
+        // TODO(b/245755256): Add another API that returns a Boolean indicating
+        //  whether requestFocus succeeded or not.
+        @OptIn(ExperimentalComposeUiApi::class)
+        findFocusTarget { it.requestFocus() }
     }
 
     /**
@@ -69,15 +71,22 @@
      * associated with this [FocusRequester].
      */
     @OptIn(ExperimentalComposeUiApi::class)
-    internal fun performRequestFocus(onFound: (FocusModifier) -> Boolean): Boolean? = when (this) {
-        Cancel -> false
-        Default -> null
-        else -> {
-            var success = false
-            focusRequesterModifierLocals.forEach {
-                it.findFocusNode()?.let { success = onFound.invoke(it) || success }
+    internal fun findFocusTarget(onFound: (FocusTargetModifierNode) -> Boolean): Boolean? {
+        return when (this) {
+            Cancel -> false
+            Default -> null
+            else -> {
+                var success: Boolean? = null
+                focusRequesterNodes.forEach { node ->
+                    node.visitChildren(Nodes.FocusTarget) {
+                        if (onFound(it)) {
+                            success = true
+                            return@forEach
+                        }
+                    }
+                }
+                success
             }
-            success
         }
     }
 
@@ -96,17 +105,15 @@
      *
      * @sample androidx.compose.ui.samples.CaptureFocusSample
      */
+    @OptIn(ExperimentalComposeUiApi::class)
     fun captureFocus(): Boolean {
-        check(focusRequesterModifierLocals.isNotEmpty()) { focusRequesterNotInitialized }
-        var success = false
-        focusRequesterModifierLocals.forEach {
-            it.findFocusNode()?.apply {
-                if (captureFocus()) {
-                    success = true
-                }
+        check(focusRequesterNodes.isNotEmpty()) { focusRequesterNotInitialized }
+        focusRequesterNodes.forEach {
+            if (it.captureFocus()) {
+                return true
             }
         }
-        return success
+        return false
     }
 
     /**
@@ -123,17 +130,15 @@
      *
      * @sample androidx.compose.ui.samples.CaptureFocusSample
      */
+    @OptIn(ExperimentalComposeUiApi::class)
     fun freeFocus(): Boolean {
-        check(focusRequesterModifierLocals.isNotEmpty()) { focusRequesterNotInitialized }
-        var success = false
-        focusRequesterModifierLocals.forEach {
-            it.findFocusNode()?.apply {
-                if (freeFocus()) {
-                    success = true
-                }
+        check(focusRequesterNodes.isNotEmpty()) { focusRequesterNotInitialized }
+        focusRequesterNodes.forEach {
+            if (it.freeFocus()) {
+                return true
             }
         }
-        return success
+        return false
     }
 
     companion object {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt
index 664b1ec..757a52a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt
@@ -16,18 +16,20 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.runtime.collection.MutableVector
-import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalProvider
-import androidx.compose.ui.modifier.ModifierLocalReadScope
-import androidx.compose.ui.modifier.ProvidableModifierLocal
-import androidx.compose.ui.modifier.modifierLocalOf
-import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.modifierElementOf
+import androidx.compose.ui.node.visitChildren
+
+/**
+ * Implement this interface to create a modifier node that can be used to request changes in
+ * the focus state of a [FocusTargetModifierNode] down the hierarchy.
+ */
+@ExperimentalComposeUiApi
+interface FocusRequesterModifierNode : DelegatableNode
 
 /**
  * A [modifier][Modifier.Element] that is used to pass in a [FocusRequester] that can be used to
@@ -38,6 +40,7 @@
  * @see FocusRequester
  * @see Modifier.focusRequester
  */
+@Deprecated("Use FocusRequesterModifierNode instead")
 @JvmDefaultWithCompatibility
 interface FocusRequesterModifier : Modifier.Element {
     /**
@@ -48,78 +51,81 @@
     val focusRequester: FocusRequester
 }
 
-internal val ModifierLocalFocusRequester = modifierLocalOf<FocusRequesterModifierLocal?> { null }
-
-internal class FocusRequesterModifierLocal(
-    val focusRequester: FocusRequester
-) : ModifierLocalConsumer, ModifierLocalProvider<FocusRequesterModifierLocal?> {
-    private var parent: FocusRequesterModifierLocal? = null
-    private val focusModifiers = mutableVectorOf<FocusModifier>()
-
-    init {
-        focusRequester.focusRequesterModifierLocals += this
+/**
+ * Use this function to request focus. If the system grants focus to a component associated
+ * with this [FocusRequester], its [onFocusChanged] modifiers will receive a [FocusState] object
+ * where [FocusState.isFocused] is true.
+ *
+ * @sample androidx.compose.ui.samples.RequestFocusSample
+ */
+@ExperimentalComposeUiApi
+fun FocusRequesterModifierNode.requestFocus(): Boolean {
+    visitChildren(Nodes.FocusTarget) {
+        if (it.requestFocus()) return true
     }
+    return false
+}
 
-    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) = with(scope) {
-        val newParent = ModifierLocalFocusRequester.current
-        if (newParent != parent) {
-            parent?.removeFocusModifiers(focusModifiers)
-            newParent?.addFocusModifiers(focusModifiers)
-            parent = newParent
+/**
+ * Deny requests to clear focus.
+ *
+ * Use this function to send a request to capture focus. If a component captures focus,
+ * it will send a [FocusState] object to its associated [onFocusChanged]
+ * modifiers where [FocusState.isCaptured]() == true.
+ *
+ * When a component is in a Captured state, all focus requests from other components are
+ * declined.
+ *
+ * @return true if the focus was successfully captured by one of the
+ * [focus][focusTarget] modifiers associated with this [FocusRequester]. False otherwise.
+ *
+ * @sample androidx.compose.ui.samples.CaptureFocusSample
+ */
+@ExperimentalComposeUiApi
+fun FocusRequesterModifierNode.captureFocus(): Boolean {
+    visitChildren(Nodes.FocusTarget) {
+        if (it.captureFocus()) {
+            // it.refreshFocusEventNodes()
+            return true
         }
     }
+    return false
+}
 
-    override val key: ProvidableModifierLocal<FocusRequesterModifierLocal?>
-        get() = ModifierLocalFocusRequester
-    override val value: FocusRequesterModifierLocal
-        get() = this
+/**
+ * Use this function to send a request to free focus when one of the components associated
+ * with this [FocusRequester] is in a Captured state. If a component frees focus,
+ * it will send a [FocusState] object to its associated [onFocusChanged]
+ * modifiers where [FocusState.isCaptured]() == false.
+ *
+ * When a component is in a Captured state, all focus requests from other components are
+ * declined.
+ *.
+ * @return true if the captured focus was successfully released. i.e. At the end of this
+ * operation, one of the components associated with this [focusRequester] freed focus.
+ *
+ * @sample androidx.compose.ui.samples.CaptureFocusSample
+ */
+@ExperimentalComposeUiApi
+fun FocusRequesterModifierNode.freeFocus(): Boolean {
+    visitChildren(Nodes.FocusTarget) {
+        if (it.freeFocus()) return true
+    }
+    return false
+}
 
-    fun addFocusModifier(focusModifier: FocusModifier) {
-        focusModifiers += focusModifier
-        parent?.addFocusModifier(focusModifier)
+@OptIn(ExperimentalComposeUiApi::class)
+internal class FocusRequesterModifierNodeImpl(
+    var focusRequester: FocusRequester
+) : FocusRequesterModifierNode, Modifier.Node() {
+    override fun onAttach() {
+        super.onAttach()
+        focusRequester.focusRequesterNodes += this
     }
 
-    fun addFocusModifiers(newModifiers: MutableVector<FocusModifier>) {
-        focusModifiers.addAll(newModifiers)
-        parent?.addFocusModifiers(newModifiers)
-    }
-
-    fun removeFocusModifier(focusModifier: FocusModifier) {
-        focusModifiers -= focusModifier
-        parent?.removeFocusModifier(focusModifier)
-    }
-
-    fun removeFocusModifiers(removedModifiers: MutableVector<FocusModifier>) {
-        focusModifiers.removeAll(removedModifiers)
-        parent?.removeFocusModifiers(removedModifiers)
-    }
-
-    @Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-    fun findFocusNode(): FocusModifier? {
-        // find the first child:
-        val first = focusModifiers.fold(null as FocusModifier?) { mod1, mod2 ->
-            var layoutNode1 = mod1?.coordinator?.layoutNode ?: return@fold mod2
-            var layoutNode2 = mod2.coordinator?.layoutNode ?: return@fold mod1
-
-            while (layoutNode1.depth > layoutNode2.depth) {
-                layoutNode1 = layoutNode1.parent!!
-            }
-
-            while (layoutNode2.depth > layoutNode1.depth) {
-                layoutNode2 = layoutNode2.parent!!
-            }
-
-            while (layoutNode1.parent != layoutNode2.parent) {
-                layoutNode1 = layoutNode1.parent!!
-                layoutNode2 = layoutNode2.parent!!
-            }
-            val children = layoutNode1.parent!!.children
-            val index1 = children.indexOf(layoutNode1)
-            val index2 = children.indexOf(layoutNode2)
-            if (index1 < index2) mod1 else mod2
-        }
-
-        return first
+    override fun onDetach() {
+        focusRequester.focusRequesterNodes -= this
+        super.onDetach()
     }
 }
 
@@ -128,12 +134,20 @@
  *
  * @sample androidx.compose.ui.samples.RequestFocusSample
  */
-fun Modifier.focusRequester(focusRequester: FocusRequester): Modifier =
-    composed(debugInspectorInfo {
-        name = "focusRequester"
-        properties["focusRequester"] = focusRequester
-    }) {
-        remember(focusRequester) {
-            FocusRequesterModifierLocal(focusRequester)
+@Suppress("ModifierInspectorInfo") // b/251831790.
+fun Modifier.focusRequester(focusRequester: FocusRequester): Modifier = this.then(
+    @OptIn(ExperimentalComposeUiApi::class)
+    modifierElementOf(
+        key = focusRequester,
+        create = { FocusRequesterModifierNodeImpl(focusRequester) },
+        update = {
+            it.focusRequester.focusRequesterNodes -= it
+            it.focusRequester = focusRequester
+            it.focusRequester.focusRequesterNodes += it
+        },
+        definitions = {
+            name = "focusRequester"
+            properties["focusRequester"] = focusRequester
         }
-    }
+    )
+)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
index 4dd57ea..1400773 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
@@ -17,8 +17,8 @@
 package androidx.compose.ui.focus
 
 /**
- * The focus state of a [FocusModifier]. Use [onFocusChanged] or [onFocusEvent] modifiers to
- * access [FocusState].
+ * The focus state of a [FocusTargetModifierNode]. Use [onFocusChanged] or [onFocusEvent] modifiers
+ * to access [FocusState].
  *
  * @sample androidx.compose.ui.samples.FocusableSample
  */
@@ -69,12 +69,6 @@
      */
     Captured,
 
-    /** The focusable component is not currently focusable. (eg. A disabled button). */
-    Deactivated,
-
-    /** One of the descendants of this deactivated component is Active. */
-    DeactivatedParent,
-
     /**
      * The focusable component does not receive any key events. (ie it is not active, nor are any
      * of its descendants active).
@@ -84,30 +78,18 @@
     override val isFocused: Boolean
         get() = when (this) {
             Captured, Active -> true
-            ActiveParent, Deactivated, DeactivatedParent, Inactive -> false
+            ActiveParent, Inactive -> false
         }
 
     override val hasFocus: Boolean
         get() = when (this) {
-            Active, ActiveParent, Captured, DeactivatedParent -> true
-            Deactivated, Inactive -> false
+            Active, ActiveParent, Captured -> true
+            Inactive -> false
         }
 
     override val isCaptured: Boolean
         get() = when (this) {
             Captured -> true
-            Active, ActiveParent, Deactivated, DeactivatedParent, Inactive -> false
-        }
-
-    /**
-     * Whether the focusable component is deactivated.
-     *
-     * TODO(ralu): Consider making this public when we can add methods to interfaces without
-     * breaking compatibility.
-     */
-     val isDeactivated: Boolean
-        get() = when (this) {
-            Active, ActiveParent, Captured, Inactive -> false
-            Deactivated, DeactivatedParent -> true
+            Active, ActiveParent, Inactive -> false
         }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
index 2eea72e..831b425 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
@@ -20,77 +20,43 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
+import androidx.compose.ui.node.Nodes.FocusTarget
+import androidx.compose.ui.node.nearestAncestor
+import androidx.compose.ui.node.observeReads
 
 /**
  * Request focus for this node.
  *
- * In Compose, the parent [FocusNode][FocusModifier] controls focus for its focusable
+ * In Compose, the parent [FocusNode][FocusTargetModifierNode] controls focus for its focusable
  * children. Calling this function will send a focus request to this
- * [FocusNode][FocusModifier]'s parent [FocusNode][FocusModifier].
+ * [FocusNode][FocusTargetModifierNode]'s parent [FocusNode][FocusTargetModifierNode].
  */
-internal fun FocusModifier.requestFocus() {
-    if (coordinator?.layoutNode?.owner == null) {
-        // Not placed yet. Try requestFocus() after placement.
-        focusRequestedOnPlaced = true
-        return
+@ExperimentalComposeUiApi
+internal fun FocusTargetModifierNode.requestFocus(): Boolean {
+    check(node.isAttached)
+    val focusProperties = fetchFocusProperties()
+    // If the node is deactivated, we perform a moveFocus(Enter).
+    if (!focusProperties.canFocus) {
+        return findChildCorrespondingToFocusEnter(FocusDirection.Enter) {
+            it.requestFocus()
+        }
     }
-    when (focusState) {
+    when (focusStateImpl) {
         Active, Captured -> {
             // There is no change in focus state, but we send a focus event to notify the user
             // that the focus request is completed.
-            sendOnFocusEvent()
+            refreshFocusEventNodes()
+            return true
         }
-        ActiveParent -> if (clearChildFocus()) grantFocus()
-        Deactivated, DeactivatedParent -> {
-            // If the node is deactivated, we perform a moveFocus(Enter).
-            @OptIn(ExperimentalComposeUiApi::class)
-            findChildCorrespondingToFocusEnter(FocusDirection.Enter) {
-                it.requestFocus()
-                true
-            }
+        ActiveParent -> return (clearChildFocus() && grantFocus()).also { success ->
+            if (success) refreshFocusEventNodes()
         }
-        Inactive -> {
-            val focusParent = parent
-            if (focusParent != null) {
-                focusParent.requestFocusForChild(this)
-            } else if (requestFocusForOwner()) {
-                grantFocus()
-            }
-        }
-    }
-}
-
-/**
- * Activate this node so that it can be focused.
- *
- * Deactivated nodes are excluded from focus search, and reject requests to gain focus.
- * Calling this function activates a deactivated node.
- */
-internal fun FocusModifier.activateNode() {
-    when (focusState) {
-        ActiveParent, Active, Captured, Inactive -> {}
-        Deactivated -> focusState = Inactive
-        DeactivatedParent -> focusState = ActiveParent
-    }
-}
-
-/**
- * Deactivate this node so that it can't be focused.
- *
- * Deactivated nodes are excluded from focus search.
- */
-internal fun FocusModifier.deactivateNode() {
-    when (focusState) {
-        ActiveParent -> focusState = DeactivatedParent
-        Active, Captured -> {
-            coordinator?.layoutNode?.owner?.focusManager?.clearFocus(force = true)
-            focusState = Deactivated
-        }
-        Inactive -> focusState = Deactivated
-        Deactivated, DeactivatedParent -> {}
+        Inactive -> return nearestAncestor(FocusTarget)
+                ?.requestFocusForChild(this)
+                ?: (requestFocusForOwner() && grantFocus()).also { success ->
+                    if (success) refreshFocusEventNodes()
+                }
     }
 }
 
@@ -102,13 +68,15 @@
  *
  * @return true if the focus was successfully captured. False otherwise.
  */
-internal fun FocusModifier.captureFocus() = when (focusState) {
+@ExperimentalComposeUiApi
+internal fun FocusTargetModifierNode.captureFocus() = when (focusStateImpl) {
     Active -> {
-        focusState = Captured
+        focusStateImpl = Captured
+        refreshFocusEventNodes()
         true
     }
     Captured -> true
-    ActiveParent, Deactivated, DeactivatedParent, Inactive -> false
+    ActiveParent, Inactive -> false
 }
 
 /**
@@ -118,63 +86,60 @@
  *
  * @return true if the captured focus was released. False Otherwise.
  */
-internal fun FocusModifier.freeFocus() = when (focusState) {
+@ExperimentalComposeUiApi
+internal fun FocusTargetModifierNode.freeFocus() = when (focusStateImpl) {
     Captured -> {
-        focusState = Active
+        focusStateImpl = Active
+        refreshFocusEventNodes()
         true
     }
     Active -> true
-    ActiveParent, Deactivated, DeactivatedParent, Inactive -> false
+    ActiveParent, Inactive -> false
 }
 
 /**
  * This function clears focus from this node.
  *
- * Note: This function should only be called by a parent [focus node][FocusModifier] to
- * clear focus from one of its child [focus node][FocusModifier]s. It does not change the
+ * Note: This function should only be called by a parent [focus node][FocusTargetModifierNode] to
+ * clear focus from one of its child [focus node][FocusTargetModifierNode]s. It does not change the
  * state of the parent.
  */
-internal fun FocusModifier.clearFocus(forcedClear: Boolean = false): Boolean {
-    return when (focusState) {
-        Active -> {
-            focusState = Inactive
-            true
-        }
-        /**
-         * If the node is [ActiveParent], we need to clear focus from the [Active] descendant
-         * first, before clearing focus from this node.
-         */
-        ActiveParent -> if (clearChildFocus()) {
-            focusState = Inactive
-            true
-        } else {
-            false
-        }
-        /**
-         * If the node is [DeactivatedParent], we need to clear focus from the [Active] descendant
-         * first, before clearing focus from this node.
-         */
-        DeactivatedParent -> if (clearChildFocus()) {
-            focusState = Deactivated
-            true
-        } else {
-            false
-        }
-
-        /**
-         * If the node is [Captured], deny requests to clear focus, except for a forced clear.
-         */
-        Captured -> {
-            if (forcedClear) {
-                focusState = Inactive
-            }
-            forcedClear
-        }
-        /**
-         * Nothing to do if the node is not focused.
-         */
-        Inactive, Deactivated -> true
+@ExperimentalComposeUiApi
+internal fun FocusTargetModifierNode.clearFocus(
+    forced: Boolean = false,
+    refreshFocusEvents: Boolean
+): Boolean = when (focusStateImpl) {
+    Active -> {
+        focusStateImpl = Inactive
+        if (refreshFocusEvents) refreshFocusEventNodes()
+        true
     }
+    /**
+     * If the node is [ActiveParent], we need to clear focus from the [Active] descendant
+     * first, before clearing focus from this node.
+     */
+    ActiveParent -> if (clearChildFocus(forced, refreshFocusEvents)) {
+        focusStateImpl = Inactive
+        if (refreshFocusEvents) refreshFocusEventNodes()
+        true
+    } else {
+        false
+    }
+
+    /**
+     * If the node is [Captured], deny requests to clear focus, except for a forced clear.
+     */
+    Captured -> {
+        if (forced) {
+            focusStateImpl = Inactive
+            if (refreshFocusEvents) refreshFocusEventNodes()
+        }
+        forced
+    }
+    /**
+     * Nothing to do if the node is not focused.
+     */
+    Inactive -> true
 }
 
 /**
@@ -182,82 +147,84 @@
  * Note: This is a private function that just changes the state of this node and does not affect any
  * other nodes in the hierarchy.
  */
-private fun FocusModifier.grantFocus() {
+@OptIn(ExperimentalComposeUiApi::class)
+private fun FocusTargetModifierNode.grantFocus(): Boolean {
+    // When we grant focus to this node, we need to observe changes to the canFocus property.
+    // If canFocus is set to false, we need to clear focus.
+    observeReads { fetchFocusProperties() }
     // No Focused Children, or we don't want to propagate focus to children.
-    focusState = when (focusState) {
-        Inactive, Active, ActiveParent -> Active
-        Captured -> Captured
-        Deactivated, DeactivatedParent -> error("Granting focus to a deactivated node.")
+    when (focusStateImpl) {
+        Inactive, ActiveParent -> focusStateImpl = Active
+        Active, Captured -> { /* Already focused. */ }
     }
-}
-
-/**
- * This function grants focus to the specified child.
- * Note: This is a private function and should only be called by a parent to grant focus to one of
- * its child. It does not affect any other nodes in the hierarchy.
- */
-private fun FocusModifier.grantFocusToChild(childNode: FocusModifier): Boolean {
-    // It's very important that the child node is set before dispatching grantFocus, otherwise a
-    // child may end up indirectly trying to walk the focus tree and get a null child.
-    focusedChild = childNode
-    childNode.grantFocus()
     return true
 }
 
 /** This function clears any focus from the focused child. */
-private fun FocusModifier.clearChildFocus(): Boolean {
-    return if (requireNotNull(focusedChild).clearFocus()) {
-        focusedChild = null
-        true
-    } else {
-        false
-    }
+@ExperimentalComposeUiApi
+private fun FocusTargetModifierNode.clearChildFocus(
+    forced: Boolean = false,
+    refreshFocusEvents: Boolean = true
+): Boolean {
+    return activeChild?.clearFocus(forced, refreshFocusEvents) ?: true
 }
 
 /**
- * Focusable children of this [focus node][FocusModifier] can use this function to request
+ * Focusable children of this [focus node][FocusTargetModifierNode] can use this function to request
  * focus.
  *
  * @param childNode: The node that is requesting focus.
  * @return true if focus was granted, false otherwise.
  */
-private fun FocusModifier.requestFocusForChild(childNode: FocusModifier): Boolean {
+@OptIn(ExperimentalComposeUiApi::class)
+private fun FocusTargetModifierNode.requestFocusForChild(
+    childNode: FocusTargetModifierNode
+): Boolean {
 
     // Only this node's children can ask for focus.
-    if (childNode !in children) {
+    if (childNode.nearestAncestor(FocusTarget) != this) {
         error("Non child node cannot request focus.")
     }
 
-    return when (focusState) {
+    return when (focusStateImpl) {
         // If this node is [Active], it can give focus to the requesting child.
-        Active -> {
-            focusState = ActiveParent
-            grantFocusToChild(childNode)
+        Active -> childNode.grantFocus().also { success ->
+            if (success) {
+                focusStateImpl = ActiveParent
+                childNode.refreshFocusEventNodes()
+                refreshFocusEventNodes()
+            }
         }
         // If this node is [ActiveParent] ie, one of the parent's descendants is [Active],
         // remove focus from the currently focused child and grant it to the requesting child.
-        ActiveParent -> if (clearChildFocus()) grantFocusToChild(childNode) else false
-
-        DeactivatedParent -> when {
-            // DeactivatedParent && NoFocusChild is used to indicate an intermediate state where
-            // this parent requested focus so that it can transfer it to a child.
-            focusedChild == null -> grantFocusToChild(childNode)
-            clearChildFocus() -> grantFocusToChild(childNode)
-            else -> false
+        ActiveParent -> {
+            checkNotNull(activeChild)
+            (clearChildFocus() && childNode.grantFocus()).also { success ->
+                if (success) childNode.refreshFocusEventNodes()
+            }
         }
         // If this node is not [Active], we must gain focus first before granting it
         // to the requesting child.
         Inactive -> {
-            val focusParent = parent
+            val focusParent = nearestAncestor(FocusTarget)
             when {
                 // If this node is the root, request focus from the compose owner.
                 focusParent == null && requestFocusForOwner() -> {
-                    focusState = Active
+                    focusStateImpl = Active
+                    refreshFocusEventNodes()
                     requestFocusForChild(childNode)
                 }
                 // For non-root nodes, request focus for this node before the child.
-                focusParent != null && focusParent.requestFocusForChild(this) ->
-                    requestFocusForChild(childNode)
+                // We request focus even if this is a deactivated node, as we will end up taking
+                // focus away and granting it to the child.
+                focusParent != null && focusParent.requestFocusForChild(this) -> {
+                    requestFocusForChild(childNode).also {
+                        // Verify that focus state was granted to the child.
+                        // If this child didn't take focus then we can end up in a situation where
+                        // a deactivated parent is focused.
+                        check(this.focusState == ActiveParent)
+                    }
+                }
 
                 // Could not gain focus, so have no focus to give.
                 else -> false
@@ -265,24 +232,10 @@
         }
         // If this node is [Captured], decline requests from the children.
         Captured -> false
-        // If this node is [Deactivated], send a requestFocusForChild to its parent to attempt to
-        // change its state to [DeactivatedParent] before granting focus to the child.
-        Deactivated -> {
-            activateNode()
-            val childGrantedFocus = requestFocusForChild(childNode)
-            deactivateNode()
-            childGrantedFocus
-        }
     }
 }
 
-private fun FocusModifier.requestFocusForOwner(): Boolean {
+@OptIn(ExperimentalComposeUiApi::class)
+private fun FocusTargetModifierNode.requestFocusForOwner(): Boolean {
     return coordinator?.layoutNode?.owner?.requestFocus() ?: error("Owner not initialized.")
 }
-
-/**
- * Send the current [FocusModifier.focusState] to all [onFocusEvent] listeners.
- */
-internal fun FocusModifier.sendOnFocusEvent() {
-    focusEventListener?.propagateFocusEvent()
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
index 4e8d3db..c5e7f0b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.focus
 
 import androidx.compose.runtime.collection.MutableVector
-import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.focus.FocusDirection.Companion.Down
 import androidx.compose.ui.focus.FocusDirection.Companion.Enter
@@ -32,12 +31,14 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.input.key.KeyInputModifier
 import androidx.compose.ui.layout.findRootCoordinates
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.visitAncestors
+import androidx.compose.ui.node.visitChildren
+import androidx.compose.ui.node.visitSubtreeIf
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.LayoutDirection.Ltr
 import androidx.compose.ui.unit.LayoutDirection.Rtl
@@ -174,10 +175,11 @@
  * @param onFound This lambda is invoked if focus search finds the next focus node.
  * @return if no focus node is found, we return false. otherwise we return the result of [onFound].
  */
-internal fun FocusModifier.focusSearch(
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun FocusTargetModifierNode.focusSearch(
     focusDirection: FocusDirection,
     layoutDirection: LayoutDirection,
-    onFound: (FocusModifier) -> Boolean
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean {
     return when (focusDirection) {
         Next, Previous -> oneDimensionalFocusSearch(focusDirection, onFound)
@@ -189,105 +191,95 @@
             findActiveFocusNode()?.twoDimensionalFocusSearch(direction, onFound) ?: false
         }
         @OptIn(ExperimentalComposeUiApi::class)
-        Exit -> findActiveFocusNode()?.findActiveParent().let {
-            if (it == this || it == null) false else onFound.invoke(it)
+        Exit -> findActiveFocusNode()?.findNonDeactivatedParent().let {
+            if (it == null || it == this) false else onFound.invoke(it)
         }
         else -> error(invalidFocusDirection)
     }
 }
 
-@Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-internal fun FocusModifier.findActiveFocusNode(): FocusModifier? {
-    return when (focusState) {
-        Active, Captured -> this
-        ActiveParent, DeactivatedParent -> focusedChild?.findActiveFocusNode()
-        Inactive, Deactivated -> null
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun FocusTargetModifierNode.findActiveFocusNode(): FocusTargetModifierNode? {
+    when (focusStateImpl) {
+        Active, Captured -> return this
+        ActiveParent -> {
+            visitChildren(Nodes.FocusTarget) { node ->
+                node.findActiveFocusNode()?.let { return it }
+            }
+            return null
+        }
+        Inactive -> return null
     }
 }
 
 @Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-internal fun FocusModifier.findActiveParent(): FocusModifier? = parent?.let {
-        when (focusState) {
-            Active, Captured, Deactivated, DeactivatedParent, Inactive -> it.findActiveParent()
-            ActiveParent -> this
-        }
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun FocusTargetModifierNode.findNonDeactivatedParent(): FocusTargetModifierNode? {
+    visitAncestors(Nodes.FocusTarget) {
+        if (it.fetchFocusProperties().canFocus) return it
     }
+    return null
+}
 
 /**
  * Returns the bounding box of the focus layout area in the root or [Rect.Zero] if the
  * FocusModifier has not had a layout.
  */
-internal fun FocusModifier.focusRect(): Rect = coordinator?.let {
+@ExperimentalComposeUiApi
+internal fun FocusTargetModifierNode.focusRect(): Rect = coordinator?.let {
     it.findRootCoordinates().localBoundingBoxOf(it, clipBounds = false)
 } ?: Rect.Zero
+
 /**
- * Returns all [FocusModifier] children that are not [FocusStateImpl.isDeactivated]. Any
+ * Returns all [FocusTargetModifierNode] children that are not Deactivated. Any
  * child that is deactivated will add activated children instead, unless the deactivated
  * node has a custom Enter specified.
  */
-internal fun FocusModifier.activatedChildren(): MutableVector<FocusModifier> {
-    if (!children.any { it.focusState.isDeactivated }) {
-        return children
-    }
-    val activated = mutableVectorOf<FocusModifier>()
-    children.forEach { child ->
-        if (!child.focusState.isDeactivated) {
-            activated += child
-        } else {
-            // When we encounter a deactivated child, we add all its children,
-            // unless a custom Enter is specified.
-            @OptIn(ExperimentalComposeUiApi::class)
-            when (val customEnter = child.focusProperties.enter(Enter)) {
-                Cancel -> return mutableVectorOf()
-                Default -> activated.addAll(child.activatedChildren())
-                else -> customEnter.focusRequesterModifierLocals.forEach {
-                    it.findFocusNode()?.let { activated.add(it) }
-                }
+@ExperimentalComposeUiApi
+internal fun DelegatableNode.collectAccessibleChildren(
+    accessibleChildren: MutableVector<FocusTargetModifierNode>
+) {
+    visitSubtreeIf(Nodes.FocusTarget) {
+
+        if (it.fetchFocusProperties().canFocus) {
+            accessibleChildren.add(it)
+            return@visitSubtreeIf false
+        }
+
+        // If we encounter a deactivated child, we mimic a moveFocus(Enter).
+        when (val customEnter = it.fetchFocusProperties().enter(Enter)) {
+            // If the user declined a custom enter, omit this part of the tree.
+            Cancel -> return@visitSubtreeIf false
+
+            // If there is no custom enter, we consider all the children.
+            Default -> return@visitSubtreeIf true
+
+            else -> customEnter.focusRequesterNodes.forEach { node ->
+                node.collectAccessibleChildren(accessibleChildren)
             }
         }
+        false
     }
-    return activated
-}
-
-/**
- * Returns the inner-most KeyInputModifier on the same LayoutNode as this FocusModifier.
- */
-@Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-internal fun FocusModifier.findLastKeyInputModifier(): KeyInputModifier? {
-    val layoutNode = coordinator?.layoutNode ?: return null
-    var best: KeyInputModifier? = null
-    keyInputChildren.forEach { keyInputModifier ->
-        if (keyInputModifier.layoutNode == layoutNode) {
-            best = lastOf(keyInputModifier, best)
-        }
-    }
-    if (best != null) {
-        return best
-    }
-    // There isn't a KeyInputModifier after this, but there may be one before this.
-    return keyInputModifier
 }
 
 /**
  * Whether this node should be considered when searching for the next item during a traversal.
  */
-internal val FocusModifier.isEligibleForFocusSearch: Boolean
+@ExperimentalComposeUiApi
+internal val FocusTargetModifierNode.isEligibleForFocusSearch: Boolean
     get() = coordinator?.layoutNode?.isPlaced == true &&
-            coordinator?.layoutNode?.isAttached == true
+        coordinator?.layoutNode?.isAttached == true
 
-/**
- * Returns [one] if it comes after [two] in the modifier chain or [two] if it comes after [one].
- */
-@Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-private fun lastOf(one: KeyInputModifier, two: KeyInputModifier?): KeyInputModifier {
-    var mod = two ?: return one
-    val layoutNode = one.layoutNode
-    while (mod != one) {
-        val parent = mod.parent
-        if (parent == null || parent.layoutNode != layoutNode) {
-            return one
+@ExperimentalComposeUiApi
+internal val FocusTargetModifierNode.activeChild: FocusTargetModifierNode?
+    get() {
+        if (!node.isAttached) return null
+
+        visitChildren(Nodes.FocusTarget) {
+            when (it.focusStateImpl) {
+                Active, ActiveParent, Captured -> return it
+                Inactive -> return@visitChildren
+            }
         }
-        mod = parent
+        return null
     }
-    return two
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
index 5b0c82b..70b237c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
@@ -18,63 +18,69 @@
 
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.focus.FocusDirection.Companion.Next
 import androidx.compose.ui.focus.FocusDirection.Companion.Previous
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.node.LayoutNode
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.nearestAncestor
+import androidx.compose.ui.node.visitChildren
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 
 private const val InvalidFocusDirection = "This function should only be used for 1-D focus search"
 private const val NoActiveChild = "ActiveParent must have a focusedChild"
 
-internal fun FocusModifier.oneDimensionalFocusSearch(
+@ExperimentalComposeUiApi
+internal fun FocusTargetModifierNode.oneDimensionalFocusSearch(
     direction: FocusDirection,
-    onFound: (FocusModifier) -> Boolean
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean = when (direction) {
     Next -> forwardFocusSearch(onFound)
     Previous -> backwardFocusSearch(onFound)
     else -> error(InvalidFocusDirection)
 }
 
-private fun FocusModifier.forwardFocusSearch(
-    onFound: (FocusModifier) -> Boolean
-): Boolean = when (focusState) {
-    ActiveParent, DeactivatedParent -> {
-        val focusedChild = focusedChild ?: error(NoActiveChild)
+@ExperimentalComposeUiApi
+private fun FocusTargetModifierNode.forwardFocusSearch(
+    onFound: (FocusTargetModifierNode) -> Boolean
+): Boolean = when (focusStateImpl) {
+    ActiveParent -> {
+        val focusedChild = activeChild ?: error(NoActiveChild)
         focusedChild.forwardFocusSearch(onFound) ||
             generateAndSearchChildren(focusedChild, Next, onFound)
     }
-    Active, Captured, Deactivated -> pickChildForForwardSearch(onFound)
-    Inactive -> onFound.invoke(this)
+    Active, Captured -> pickChildForForwardSearch(onFound)
+    Inactive -> if (fetchFocusProperties().canFocus) {
+        onFound.invoke(this)
+    } else {
+        pickChildForForwardSearch(onFound)
+    }
 }
 
-private fun FocusModifier.backwardFocusSearch(
-    onFound: (FocusModifier) -> Boolean
-): Boolean = when (focusState) {
-    ActiveParent, DeactivatedParent -> {
-        val focusedChild = focusedChild ?: error(NoActiveChild)
+@ExperimentalComposeUiApi
+private fun FocusTargetModifierNode.backwardFocusSearch(
+    onFound: (FocusTargetModifierNode) -> Boolean
+): Boolean = when (focusStateImpl) {
+    ActiveParent -> {
+        val focusedChild = activeChild ?: error(NoActiveChild)
 
         // Unlike forwardFocusSearch, backwardFocusSearch visits the children before the parent.
-        when (focusedChild.focusState) {
-            ActiveParent -> focusedChild.backwardFocusSearch(onFound) ||
-                // Don't forget to visit this item after visiting all its children.
-                onFound.invoke(focusedChild)
-
-            DeactivatedParent -> focusedChild.backwardFocusSearch(onFound) ||
-                // Since this item is deactivated, just skip it and search among its siblings.
-                generateAndSearchChildren(focusedChild, Previous, onFound)
+        when (focusedChild.focusStateImpl) {
+            ActiveParent ->
+                focusedChild.backwardFocusSearch(onFound) ||
+                generateAndSearchChildren(focusedChild, Previous, onFound) ||
+                (fetchFocusProperties().canFocus && onFound.invoke(focusedChild))
 
             // Since this item "is focused", it means we already visited all its children.
             // So just search among its siblings.
             Active, Captured -> generateAndSearchChildren(focusedChild, Previous, onFound)
 
-            Deactivated, Inactive -> error(NoActiveChild)
+            Inactive -> error(NoActiveChild)
         }
     }
     // BackwardFocusSearch is invoked at the root, and so it searches among siblings of the
@@ -82,19 +88,21 @@
     // ActiveParent) or a deactivated node (instead of a deactivated parent), it indicates
     // that the hierarchy does not have focus. ie. this is the initial focus state.
     // So we pick one of the children as the result.
-    Active, Captured, Deactivated -> pickChildForBackwardSearch(onFound)
+    Active, Captured -> pickChildForBackwardSearch(onFound)
 
     // If we encounter an inactive node, we attempt to pick one of its children before picking
     // this node (backward search visits the children before the parent).
-    Inactive -> pickChildForBackwardSearch(onFound) || onFound.invoke(this)
+    Inactive -> pickChildForBackwardSearch(onFound) ||
+        if (fetchFocusProperties().canFocus) onFound.invoke(this) else false
 }
 
 // Search among your children for the next child.
 // If the next child is not found, generate more children by requesting a beyondBoundsLayout.
-private fun FocusModifier.generateAndSearchChildren(
-    focusedItem: FocusModifier,
+@ExperimentalComposeUiApi
+private fun FocusTargetModifierNode.generateAndSearchChildren(
+    focusedItem: FocusTargetModifierNode,
     direction: FocusDirection,
-    onFound: (FocusModifier) -> Boolean
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean {
     // Search among the currently available children.
     if (searchChildren(focusedItem, direction, onFound)) {
@@ -112,14 +120,18 @@
 }
 
 // Search for the next sibling that should be granted focus.
-private fun FocusModifier.searchChildren(
-    focusedItem: FocusModifier,
+@ExperimentalComposeUiApi
+private fun FocusTargetModifierNode.searchChildren(
+    focusedItem: FocusTargetModifierNode,
     direction: FocusDirection,
-    onFound: (FocusModifier) -> Boolean
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean {
-    check(focusState == ActiveParent || focusState == DeactivatedParent) {
+    check(focusStateImpl == ActiveParent) {
         "This function should only be used within a parent that has focus."
     }
+    val children = MutableVector<FocusTargetModifierNode>().apply {
+        visitChildren(Nodes.FocusTarget) { add(it) }
+    }
     children.sortWith(FocusableChildrenComparator)
     when (direction) {
         Next -> children.forEachItemAfter(focusedItem) { child ->
@@ -135,21 +147,29 @@
     // backward search, we want to move focus to the parent unless the parent is deactivated.
     // We also don't want to move focus to the root because from the user's perspective this would
     // look like nothing is focused.
-    if (direction == Next || focusState == DeactivatedParent || isRoot()) return false
+    if (direction == Next || !fetchFocusProperties().canFocus || isRoot()) return false
 
     return onFound.invoke(this)
 }
 
-private fun FocusModifier.pickChildForForwardSearch(
-    onFound: (FocusModifier) -> Boolean
+@ExperimentalComposeUiApi
+private fun FocusTargetModifierNode.pickChildForForwardSearch(
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean {
+    val children = MutableVector<FocusTargetModifierNode>().apply {
+        visitChildren(Nodes.FocusTarget) { add(it) }
+    }
     children.sortWith(FocusableChildrenComparator)
     return children.any { it.isEligibleForFocusSearch && it.forwardFocusSearch(onFound) }
 }
 
-private fun FocusModifier.pickChildForBackwardSearch(
-    onFound: (FocusModifier) -> Boolean
+@ExperimentalComposeUiApi
+private fun FocusTargetModifierNode.pickChildForBackwardSearch(
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean {
+    val children = MutableVector<FocusTargetModifierNode>().apply {
+        visitChildren(Nodes.FocusTarget) { add(it) }
+    }
     children.sortWith(FocusableChildrenComparator)
     children.forEachReversed {
         if (it.isEligibleForFocusSearch && it.backwardFocusSearch(onFound)) {
@@ -159,7 +179,8 @@
     return false
 }
 
-private fun FocusModifier.isRoot() = parent == null
+@OptIn(ExperimentalComposeUiApi::class)
+private fun FocusTargetModifierNode.isRoot() = nearestAncestor(Nodes.FocusTarget) == null
 
 @Suppress("BanInlineOptIn")
 @OptIn(ExperimentalContracts::class)
@@ -204,20 +225,24 @@
  * order index. This would be more expensive than sorting the items. In addition to this, sorting
  * the items makes the next focus search more efficient.
  */
-private object FocusableChildrenComparator : Comparator<FocusModifier> {
-    override fun compare(focusModifier1: FocusModifier?, focusModifier2: FocusModifier?): Int {
-        requireNotNull(focusModifier1)
-        requireNotNull(focusModifier2)
+@OptIn(ExperimentalComposeUiApi::class)
+private object FocusableChildrenComparator : Comparator<FocusTargetModifierNode> {
+    override fun compare(
+        focusTarget1: FocusTargetModifierNode?,
+        focusTarget2: FocusTargetModifierNode?
+    ): Int {
+        requireNotNull(focusTarget1)
+        requireNotNull(focusTarget2)
 
         // Ignore focus modifiers that won't be considered during focus search.
-        if (!focusModifier1.isEligibleForFocusSearch || !focusModifier2.isEligibleForFocusSearch) {
-            if (focusModifier1.isEligibleForFocusSearch) return -1
-            if (focusModifier2.isEligibleForFocusSearch) return 1
+        if (!focusTarget1.isEligibleForFocusSearch || !focusTarget2.isEligibleForFocusSearch) {
+            if (focusTarget1.isEligibleForFocusSearch) return -1
+            if (focusTarget2.isEligibleForFocusSearch) return 1
             return 0
         }
 
-        val layoutNode1 = checkNotNull(focusModifier1.coordinator?.layoutNode)
-        val layoutNode2 = checkNotNull(focusModifier2.coordinator?.layoutNode)
+        val layoutNode1 = checkNotNull(focusTarget1.coordinator?.layoutNode)
+        val layoutNode2 = checkNotNull(focusTarget2.coordinator?.layoutNode)
 
         // Use natural order for focus modifiers within the same layout node.
         if (layoutNode1 == layoutNode2) return 0
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
index 5ba0114..55655cb 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
@@ -26,10 +26,10 @@
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.node.Nodes
+import androidx.compose.ui.node.visitChildren
 import kotlin.math.absoluteValue
 import kotlin.math.max
 
@@ -37,38 +37,38 @@
 private const val NoActiveChild = "ActiveParent must have a focusedChild"
 
 /**
- *  Perform a search among the immediate children of this [node][FocusModifier] in the
+ *  Perform a search among the immediate children of this [node][FocusTargetModifierNode] in the
  *  specified [direction][FocusDirection] and return the node that is to be focused next. If one
  *  of the children is currently focused, we start from that point and search in the specified
  *  [direction][FocusDirection]. If none of the children are currently focused, we pick the
  *  top-left or bottom right based on the specified [direction][FocusDirection].
  */
-internal fun FocusModifier.twoDimensionalFocusSearch(
+@ExperimentalComposeUiApi
+internal fun FocusTargetModifierNode.twoDimensionalFocusSearch(
     direction: FocusDirection,
-    onFound: (FocusModifier) -> Boolean
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean {
-    when (focusState) {
-        Inactive -> return onFound.invoke(this)
-        Deactivated -> return false
-        ActiveParent, DeactivatedParent -> {
-            val focusedChild = focusedChild ?: error(NoActiveChild)
+    when (focusStateImpl) {
+        Inactive -> return if (fetchFocusProperties().canFocus) onFound.invoke(this) else false
+        ActiveParent -> {
+            val focusedChild = activeChild ?: error(NoActiveChild)
             // For 2D focus search we only search among siblings. You have to use DPad Center or
             // call moveFocus(In) to move focus to a child. So twoDimensionalFocus Search delegates
             // search to a child only if it "has focus". If this node "is focused", we just skip the
             // children and search among the siblings of the focused item by calling
             // "searchChildren" on this node.
-            when (focusedChild.focusState) {
+            when (focusedChild.focusStateImpl) {
 
-                ActiveParent, DeactivatedParent -> {
+                ActiveParent -> {
                     // If the focusedChild is an intermediate parent,
                     // we continue searching among its children.
                     if (focusedChild.twoDimensionalFocusSearch(direction, onFound)) return true
 
                     // If we don't find a match, we exit this Parent.
                     // First check if this node has a custom focus exit.
-                    @OptIn(ExperimentalComposeUiApi::class)
-                    focusedChild.focusProperties.exit(direction).performRequestFocus(onFound)
-                        ?.let { return it }
+                    focusedChild
+                        .fetchFocusProperties().exit(direction)
+                        .findFocusTarget(onFound)?.let { return it }
 
                     // If we don't have a custom exit property,
                     // we search among the siblings of the parent.
@@ -77,7 +77,7 @@
                 // Search for the next eligible sibling.
                 Active, Captured ->
                     return generateAndSearchChildren(focusedChild, direction, onFound)
-                Deactivated, Inactive -> error(NoActiveChild)
+                Inactive -> error(NoActiveChild)
             }
         }
         Active, Captured -> {
@@ -97,16 +97,17 @@
  * @param onFound the callback that is run when the child is found.
  * @return true if we find a suitable child, false otherwise.
  */
-internal fun FocusModifier.findChildCorrespondingToFocusEnter(
+@ExperimentalComposeUiApi
+internal fun FocusTargetModifierNode.findChildCorrespondingToFocusEnter(
     direction: FocusDirection,
-    onFound: (FocusModifier) -> Boolean
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean {
 
     // Check if a custom FocusEnter is specified.
-    @OptIn(ExperimentalComposeUiApi::class)
-    focusProperties.enter(direction).performRequestFocus(onFound)?.let { return it }
+    fetchFocusProperties().enter(direction).findFocusTarget(onFound)?.let { return it }
 
-    val focusableChildren = activatedChildren()
+    val focusableChildren = MutableVector<FocusTargetModifierNode>()
+    collectAccessibleChildren(focusableChildren)
 
     // If there are aren't multiple children to choose from, return the first child.
     if (focusableChildren.size <= 1) {
@@ -119,7 +120,7 @@
     val requestedDirection = when (direction) {
         // TODO(b/244528858) choose different items for moveFocus(Enter) based on LayoutDirection.
         @OptIn(ExperimentalComposeUiApi::class)
-        Enter -> Left
+        Enter -> Right
         else -> direction
     }
 
@@ -136,10 +137,11 @@
 
 // Search among your children for the next child.
 // If the next child is not found, generate more children by requesting a beyondBoundsLayout.
-private fun FocusModifier.generateAndSearchChildren(
-    focusedItem: FocusModifier,
+@ExperimentalComposeUiApi
+private fun FocusTargetModifierNode.generateAndSearchChildren(
+    focusedItem: FocusTargetModifierNode,
     direction: FocusDirection,
-    onFound: (FocusModifier) -> Boolean
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean {
     // Search among the currently available children.
     if (searchChildren(focusedItem, direction, onFound)) {
@@ -156,30 +158,33 @@
     } ?: false
 }
 
-private fun FocusModifier.searchChildren(
-    focusedItem: FocusModifier,
+@ExperimentalComposeUiApi
+private fun FocusTargetModifierNode.searchChildren(
+    focusedItem: FocusTargetModifierNode,
     direction: FocusDirection,
-    onFound: (FocusModifier) -> Boolean
+    onFound: (FocusTargetModifierNode) -> Boolean
 ): Boolean {
-    val childrenCopy = MutableVector<FocusModifier>(children.size)
-    childrenCopy.addAll(children)
-    while (childrenCopy.isNotEmpty()) {
-        val nextItem = childrenCopy.findBestCandidate(focusedItem.focusRect(), direction)
+    val children = MutableVector<FocusTargetModifierNode>().apply {
+        visitChildren(Nodes.FocusTarget) {
+            this.add(it)
+        }
+    }
+    while (children.isNotEmpty()) {
+        val nextItem = children.findBestCandidate(focusedItem.focusRect(), direction)
             ?: return false
 
         // If the result is not deactivated, this is a valid next item.
-        if (!nextItem.focusState.isDeactivated) return onFound.invoke(nextItem)
+        if (nextItem.fetchFocusProperties().canFocus) return onFound.invoke(nextItem)
 
         // If the result is deactivated, and the deactivated node has a custom Enter, we use it.
-        @OptIn(ExperimentalComposeUiApi::class)
-        nextItem.focusProperties.enter(direction).performRequestFocus(onFound)?.let { return it }
+        nextItem.fetchFocusProperties().enter(direction).findFocusTarget(onFound)?.let { return it }
 
-        // If the result is deactivated, and there is no custom ehter, we search among its children.
+        // If the result is deactivated, and there is no custom enter, we search among its children.
         if (nextItem.generateAndSearchChildren(focusedItem, direction, onFound)) return true
 
         // If there are no results among the children of the deactivated node,
         // repeat the search by excluding this deactivated node.
-        childrenCopy.remove(nextItem)
+        children.remove(nextItem)
     }
     return false
 }
@@ -188,11 +193,12 @@
 // TODO(b/182319711): For Left/Right focus moves, Consider finding the first candidate in the beam
 //  and then only comparing candidates in the beam. If nothing is in the beam, then consider all
 //  valid candidates.
+@ExperimentalComposeUiApi
 @Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-private fun MutableVector<FocusModifier>.findBestCandidate(
+private fun MutableVector<FocusTargetModifierNode>.findBestCandidate(
     focusRect: Rect,
     direction: FocusDirection
-): FocusModifier? {
+): FocusTargetModifierNode? {
     // Pick an impossible rectangle as the initial best candidate Rect.
     var bestCandidate = when (direction) {
         Left -> focusRect.translate(focusRect.width + 1, 0f)
@@ -202,7 +208,7 @@
         else -> error(InvalidFocusDirection)
     }
 
-    var searchResult: FocusModifier? = null
+    var searchResult: FocusTargetModifierNode? = null
     forEach { candidateNode ->
         if (candidateNode.isEligibleForFocusSearch) {
             val candidateRect = candidateNode.focusRect()
@@ -363,8 +369,9 @@
 private fun Rect.bottomRight() = Rect(right, bottom, right, bottom)
 
 // Find the active descendant.
+@ExperimentalComposeUiApi
 @Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-private fun FocusModifier.activeNode(): FocusModifier {
-    check(focusState == ActiveParent || focusState == DeactivatedParent)
+private fun FocusTargetModifierNode.activeNode(): FocusTargetModifierNode {
+    check(focusState == ActiveParent)
     return findActiveFocusNode() ?: error(NoActiveChild)
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/focus/FocusAwareInputModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/focus/FocusAwareInputModifier.kt
deleted file mode 100644
index a559215..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/focus/FocusAwareInputModifier.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.input.focus
-
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalProvider
-import androidx.compose.ui.modifier.ModifierLocalReadScope
-import androidx.compose.ui.modifier.ProvidableModifierLocal
-
-internal interface FocusDirectedInputEvent
-
-/**
- * A modifier that routes [FocusDirectedInputEvent]s to the currently focused item.
- *
- * The event is routed to the focused item. Before reaching the focused item, [onPreEvent]() is
- * called for parents of the focused item. If the parents don't consume the event, [onPreEvent]()
- * is called for the focused item. If the event is still not consumed, [onEvent]() is called on the
- * focused item's parents.
- */
-internal open class FocusAwareInputModifier<T : FocusDirectedInputEvent>(
-    val onEvent: ((FocusDirectedInputEvent) -> Boolean)?,
-    val onPreEvent: ((FocusDirectedInputEvent) -> Boolean)?,
-    override val key: ProvidableModifierLocal<FocusAwareInputModifier<T>?>
-) : ModifierLocalConsumer,
-    ModifierLocalProvider<FocusAwareInputModifier<T>?> {
-
-    // The focus-aware modifier that is a parent of this modifier.
-    private var focusAwareEventParent: FocusAwareInputModifier<T>? = null
-    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
-        focusAwareEventParent = with(scope) { key.current }
-    }
-    // Register this modifier as the FocusAwareParent for modifiers further down the hierarchy.
-    override val value: FocusAwareInputModifier<T>
-        get() = this
-
-    fun propagateFocusAwareEvent(event: T) = propagatePreEvent(event) || propagateEvent(event)
-
-    private fun propagatePreEvent(event: T): Boolean {
-        // We first propagate the event to the parent.
-        if (focusAwareEventParent?.propagatePreEvent(event) == true) return true
-
-        // If none of the parents consume the event, we attempt to consume it.
-        return onPreEvent?.invoke(event) ?: false
-    }
-
-    private fun propagateEvent(event: T): Boolean {
-        // We attempt to consume the key event first.
-        if (onEvent?.invoke(event) == true) return true
-
-        // If the event is not consumed, we propagate it to the parent.
-        return focusAwareEventParent?.propagateEvent(event) ?: false
-    }
-}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
index 542bf03..ef60df1 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
@@ -16,22 +16,47 @@
 
 package androidx.compose.ui.input.key
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusModifier
-import androidx.compose.ui.focus.ModifierLocalParentFocusModifier
-import androidx.compose.ui.focus.findActiveFocusNode
-import androidx.compose.ui.focus.findLastKeyInputModifier
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.OnPlacedModifier
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalProvider
-import androidx.compose.ui.modifier.ModifierLocalReadScope
-import androidx.compose.ui.modifier.ProvidableModifierLocal
-import androidx.compose.ui.modifier.modifierLocalOf
-import androidx.compose.ui.node.LayoutNode
-import androidx.compose.ui.node.NodeCoordinator
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.modifierElementOf
+
+/**
+ * Implement this interface to create a [Modifier.Node] that can intercept hardware Key events.
+ *
+ * The event is routed to the focused item. Before reaching the focused item, [onPreKeyEvent]() is
+ * called for parents of the focused item. If the parents don't consume the event, [onPreKeyEvent]()
+ * is called for the focused item. If the event is still not consumed, [onKeyEvent]() is called on
+ * the focused item's parents.
+ */
+@ExperimentalComposeUiApi
+interface KeyInputModifierNode : DelegatableNode {
+
+    /**
+     * This function is called when a [KeyEvent] is received by this node during the upward
+     * pass. While implementing this callback, return true to stop propagation of this event. If you
+     * return false, the key event will be sent to this [KeyInputModifierNode]'s parent.
+     */
+    fun onKeyEvent(event: KeyEvent): Boolean
+
+    /**
+     * This function is called when a [KeyEvent] is received by this node during the
+     * downward pass. It gives ancestors of a focused component the chance to intercept an event.
+     * Return true to stop propagation of this event. If you return false, the event will be sent
+     * to this [KeyInputModifierNode]'s child. If none of the children consume the event,
+     * it will be sent back up to the root using the [onKeyEvent] function.
+     */
+    fun onPreKeyEvent(event: KeyEvent): Boolean
+}
+
+@ExperimentalComposeUiApi
+internal class KeyInputInputModifierNodeImpl(
+    var onEvent: ((KeyEvent) -> Boolean)?,
+    var onPreEvent: ((KeyEvent) -> Boolean)?
+) : KeyInputModifierNode, Modifier.Node() {
+    override fun onKeyEvent(event: KeyEvent): Boolean = this.onEvent?.invoke(event) ?: false
+    override fun onPreKeyEvent(event: KeyEvent): Boolean = this.onPreEvent?.invoke(event) ?: false
+}
 
 /**
  * Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
@@ -43,14 +68,19 @@
  *
  * @sample androidx.compose.ui.samples.KeyEventSample
  */
-fun Modifier.onKeyEvent(onKeyEvent: (KeyEvent) -> Boolean): Modifier = inspectable(
-    inspectorInfo = debugInspectorInfo {
-        name = "onKeyEvent"
-        properties["onKeyEvent"] = onKeyEvent
-    }
-) {
-    KeyInputModifier( >
-}
+@OptIn(ExperimentalComposeUiApi::class)
+@Suppress("ModifierInspectorInfo") // b/251831790.
+fun Modifier.onKeyEvent(onKeyEvent: (KeyEvent) -> Boolean): Modifier = this.then(
+    modifierElementOf(
+        key = onKeyEvent,
+        create = { KeyInputInputModifierNodeImpl(  },
+        update = { it. },
+        definitions = {
+            name = "onKeyEvent"
+            properties["onKeyEvent"] = onKeyEvent
+        }
+    )
+)
 
 /**
  * Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
@@ -60,75 +90,20 @@
  * keyboard. It gives ancestors of a focused component the chance to intercept a [KeyEvent].
  * Return true to stop propagation of this event. If you return false, the key event will be sent
  * to this [onPreviewKeyEvent]'s child. If none of the children consume the event, it will be
- * sent back up to the root [KeyInputModifier] using the onKeyEvent callback.
+ * sent back up to the root [KeyInputModifierNode] using the onKeyEvent callback.
  *
  * @sample androidx.compose.ui.samples.KeyEventSample
  */
-fun Modifier.onPreviewKeyEvent(onPreviewKeyEvent: (KeyEvent) -> Boolean): Modifier = inspectable(
-    inspectorInfo = debugInspectorInfo {
-        name = "onPreviewKeyEvent"
-        properties["onPreviewKeyEvent"] = onPreviewKeyEvent
-    }
-) {
-    KeyInputModifier( >
-}
-
-/**
- * Used to build a tree of [KeyInputModifier]s. This contains the [KeyInputModifier] that is
- * higher in the layout tree.
- */
-internal val ModifierLocalKeyInput = modifierLocalOf<KeyInputModifier?> { null }
-
-internal class KeyInputModifier(
-    val onKeyEvent: ((KeyEvent) -> Boolean)?,
-    val onPreviewKeyEvent: ((KeyEvent) -> Boolean)?
-) : ModifierLocalConsumer, ModifierLocalProvider<KeyInputModifier?>, OnPlacedModifier {
-    private var focusModifier: FocusModifier? = null
-    var parent: KeyInputModifier? = null
-        private set
-    var layoutNode: LayoutNode? = null
-        private set
-
-    override val key: ProvidableModifierLocal<KeyInputModifier?>
-        get() = ModifierLocalKeyInput
-    override val value: KeyInputModifier
-        get() = this
-
-    fun processKeyInput(keyEvent: KeyEvent): Boolean {
-        val activeKeyInputModifier = focusModifier
-            ?.findActiveFocusNode()
-            ?.findLastKeyInputModifier()
-            ?: error("KeyEvent can't be processed because this key input node is not active.")
-        val consumed = activeKeyInputModifier.propagatePreviewKeyEvent(keyEvent)
-        return if (consumed) true else activeKeyInputModifier.propagateKeyEvent(keyEvent)
-    }
-
-    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) = with(scope) {
-        focusModifier?.keyInputChildren?.remove(this@KeyInputModifier)
-        focusModifier = ModifierLocalParentFocusModifier.current
-        focusModifier?.keyInputChildren?.add(this@KeyInputModifier)
-        parent = ModifierLocalKeyInput.current
-    }
-
-    fun propagatePreviewKeyEvent(keyEvent: KeyEvent): Boolean {
-        // We first propagate the preview key event to the parent.
-        val consumed = parent?.propagatePreviewKeyEvent(keyEvent)
-        if (consumed == true) return consumed
-
-        // If none of the parents consumed the event, we attempt to consume it.
-        return onPreviewKeyEvent?.invoke(keyEvent) ?: false
-    }
-
-    fun propagateKeyEvent(keyEvent: KeyEvent): Boolean {
-        // We attempt to consume the key event first.
-        val consumed = onKeyEvent?.invoke(keyEvent)
-        if (consumed == true) return consumed
-
-        // If the event is not consumed, we propagate it to the parent.
-        return parent?.propagateKeyEvent(keyEvent) ?: false
-    }
-
-    override fun onPlaced(coordinates: LayoutCoordinates) {
-        layoutNode = (coordinates as NodeCoordinator).layoutNode
-    }
-}
+@OptIn(ExperimentalComposeUiApi::class)
+@Suppress("ModifierInspectorInfo") // b/251831790.
+fun Modifier.onPreviewKeyEvent(onPreviewKeyEvent: (KeyEvent) -> Boolean): Modifier = this.then(
+    modifierElementOf(
+        key = onPreviewKeyEvent,
+        create = { KeyInputInputModifierNodeImpl(  },
+        update = { it. },
+        definitions = {
+            name = "onPreviewKeyEvent"
+            properties["onPreviewKeyEvent"] = onPreviewKeyEvent
+        }
+    )
+)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryInputModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryInputModifier.kt
index a8a5189..c1d6b0e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryInputModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryInputModifier.kt
@@ -18,11 +18,48 @@
 
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.focus.FocusDirectedInputEvent
-import androidx.compose.ui.input.focus.FocusAwareInputModifier
-import androidx.compose.ui.modifier.modifierLocalOf
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.platform.inspectable
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.modifierElementOf
+
+/**
+ * Implement this interface to create a [Modifier.Node] that can intercept rotary scroll events.
+ *
+ * The event is routed to the focused item. Before reaching the focused item,
+ * [onPreRotaryScrollEvent]() is called for parents of the focused item. If the parents don't
+ * consume the event, [onPreRotaryScrollEvent]() is called for the focused item. If the event is
+ * still not consumed, [onRotaryScrollEvent]() is called on the focused item's parents.
+ */
+@ExperimentalComposeUiApi
+interface RotaryInputModifierNode : DelegatableNode {
+    /**
+     * This function is called when a [RotaryScrollEvent] is received by this node during the upward
+     * pass. While implementing this callback, return true to stop propagation of this event. If you
+     * return false, the key event will be sent to this [RotaryInputModifierNode]'s parent.
+     */
+    fun onRotaryScrollEvent(event: RotaryScrollEvent): Boolean
+
+    /**
+     * This function is called when a [RotaryScrollEvent] is received by this node during the
+     * downward pass. It gives ancestors of a focused component the chance to intercept an event.
+     * Return true to stop propagation of this event. If you return false, the event will be sent
+     * to this [RotaryInputModifierNode]'s child. If none of the children consume the event,
+     * it will be sent back up to the root using the [onRotaryScrollEvent] function.
+     */
+    fun onPreRotaryScrollEvent(event: RotaryScrollEvent): Boolean
+}
+
+@ExperimentalComposeUiApi
+internal class RotaryInputModifierNodeImpl(
+    var onEvent: ((RotaryScrollEvent) -> Boolean)?,
+    var onPreEvent: ((RotaryScrollEvent) -> Boolean)?
+) : RotaryInputModifierNode, Modifier.Node() {
+    override fun onRotaryScrollEvent(event: RotaryScrollEvent): Boolean {
+        return onEvent?.invoke(event) ?: false
+    }
+    override fun onPreRotaryScrollEvent(event: RotaryScrollEvent): Boolean {
+        return onPreEvent?.invoke(event) ?: false
+    }
+}
 
 /**
  * Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
@@ -43,21 +80,21 @@
  * access to a [RotaryScrollEvent] when a child does not consume it:
  * @sample androidx.compose.ui.samples.PreRotaryEventSample
  */
+@Suppress("ModifierInspectorInfo") // b/251831790.
 @ExperimentalComposeUiApi
 fun Modifier.onRotaryScrollEvent(
     onRotaryScrollEvent: (RotaryScrollEvent) -> Boolean
-): Modifier = inspectable(
-    inspectorInfo = debugInspectorInfo {
-        name = "onRotaryScrollEvent"
-        properties["onRotaryScrollEvent"] = onRotaryScrollEvent
-    }
-) {
-    FocusAwareInputModifier(
-        >
-        >
-        key = ModifierLocalRotaryScrollParent
+): Modifier = this.then(
+    modifierElementOf(
+        key = onRotaryScrollEvent,
+        create = { RotaryInputModifierNodeImpl(  },
+        update = { it. },
+        definitions = {
+            name = "onRotaryScrollEvent"
+            properties["onRotaryScrollEvent"] = onRotaryScrollEvent
+        }
     )
-}
+)
 
 /**
  * Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
@@ -80,30 +117,20 @@
  *
  * @sample androidx.compose.ui.samples.PreRotaryEventSample
  */
+@Suppress("ModifierInspectorInfo") // b/251831790.
 @ExperimentalComposeUiApi
 fun Modifier.onPreRotaryScrollEvent(
     onPreRotaryScrollEvent: (RotaryScrollEvent) -> Boolean
-): Modifier = inspectable(
-    inspectorInfo = debugInspectorInfo {
-        name = "onPreRotaryScrollEvent"
-        properties["onPreRotaryScrollEvent"] = onPreRotaryScrollEvent
-    }
-) {
-    FocusAwareInputModifier(
-        >
-        >
-        key = ModifierLocalRotaryScrollParent
+): Modifier = this.then(
+    modifierElementOf(
+        key = onPreRotaryScrollEvent,
+        create = {
+            RotaryInputModifierNodeImpl( >
+        },
+        update = { it. },
+        definitions = {
+            name = "onPreRotaryScrollEvent"
+            properties["onPreRotaryScrollEvent"] = onPreRotaryScrollEvent
+        }
     )
-}
-
-@ExperimentalComposeUiApi
-internal val ModifierLocalRotaryScrollParent =
-    modifierLocalOf<FocusAwareInputModifier<RotaryScrollEvent>?> { null }
-
-@ExperimentalComposeUiApi
-private fun ((RotaryScrollEvent) -> Boolean).focusAwareCallback() = { e: FocusDirectedInputEvent ->
-    check(e is RotaryScrollEvent) {
-        "FocusAwareEvent is dispatched to the wrong FocusAwareParent."
-    }
-    invoke(e)
-}
+)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.kt
index e79fdd4..732b277 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.input.rotary
 
 import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.input.focus.FocusDirectedInputEvent
 
 /**
  * This event represents a rotary input event.
@@ -44,14 +43,14 @@
      * platform-dependent.
      */
     val uptimeMillis: Long
-) : FocusDirectedInputEvent {
+) {
     override fun equals(other: Any?): Boolean = other is RotaryScrollEvent &&
         other.verticalScrollPixels == verticalScrollPixels &&
         other.horizontalScrollPixels == horizontalScrollPixels &&
         other.uptimeMillis == uptimeMillis
 
     override fun hashCode(): Int = 0
-            .let { 31 * it + verticalScrollPixels.hashCode() }
+            .let { verticalScrollPixels.hashCode() }
             .let { 31 * it + horizontalScrollPixels.hashCode() }
             .let { 31 * it + uptimeMillis.hashCode() }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/BackwardsCompatNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/BackwardsCompatNode.kt
index 6fb14bb..49c7871 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/BackwardsCompatNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/BackwardsCompatNode.kt
@@ -23,9 +23,15 @@
 import androidx.compose.ui.draw.BuildDrawCacheParams
 import androidx.compose.ui.draw.DrawCacheModifier
 import androidx.compose.ui.draw.DrawModifier
+import androidx.compose.ui.focus.FocusEventModifier
+import androidx.compose.ui.focus.FocusEventModifierNode
 import androidx.compose.ui.focus.FocusOrderModifier
 import androidx.compose.ui.focus.FocusOrderModifierToProperties
-import androidx.compose.ui.focus.FocusPropertiesModifier
+import androidx.compose.ui.focus.FocusProperties
+import androidx.compose.ui.focus.FocusPropertiesModifierNode
+import androidx.compose.ui.focus.FocusRequesterModifier
+import androidx.compose.ui.focus.FocusRequesterModifierNode
+import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.input.pointer.PointerEvent
@@ -54,7 +60,6 @@
 import androidx.compose.ui.modifier.ModifierLocalProvider
 import androidx.compose.ui.modifier.ModifierLocalReadScope
 import androidx.compose.ui.modifier.modifierLocalMapOf
-import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.SemanticsConfiguration
 import androidx.compose.ui.semantics.SemanticsModifier
 import androidx.compose.ui.unit.Constraints
@@ -83,6 +88,9 @@
     ParentDataModifierNode,
     LayoutAwareModifierNode,
     GlobalPositionAwareModifierNode,
+    FocusEventModifierNode,
+    FocusPropertiesModifierNode,
+    FocusRequesterModifierNode,
     OwnerScope,
     BuildDrawCacheParams,
     Modifier.Node() {
@@ -92,7 +100,7 @@
 
     var element: Modifier.Element = element
         set(value) {
-            if (isAttached) uninitializeModifier()
+            if (isAttached) unInitializeModifier()
             field = value
             kindSet = calculateNodeKindSetFrom(value)
             if (isAttached) initializeModifier(false)
@@ -103,10 +111,10 @@
     }
 
     override fun onDetach() {
-        uninitializeModifier()
+        unInitializeModifier()
     }
 
-    private fun uninitializeModifier() {
+    private fun unInitializeModifier() {
         check(isAttached)
         val element = element
         if (isKind(Nodes.Locals)) {
@@ -118,18 +126,13 @@
             if (element is ModifierLocalConsumer) {
                 element.onModifierLocalsUpdated(DetachedModifierLocalReadScope)
             }
-            if (element is FocusOrderModifier) {
-                val focusOrderElement = focusOrderElement
-                if (focusOrderElement != null) {
-                    requireOwner()
-                        .modifierLocalManager
-                        .removedProvider(this, focusOrderElement.key)
-                }
-            }
         }
         if (isKind(Nodes.Semantics)) {
             requireOwner().onSemanticsChange()
         }
+        if (element is FocusRequesterModifier) {
+            element.focusRequester.focusRequesterNodes -= this
+        }
     }
 
     private fun initializeModifier(duringAttach: Boolean) {
@@ -145,24 +148,6 @@
                 else
                     sideEffect { updateModifierLocalConsumer() }
             }
-            // Special handling for FocusOrderModifier -- we have to use modifier local
-            // consumers and providers for it.
-            if (element is FocusOrderModifier) {
-                // Have to create a new consumer/provider
-                val scope = FocusOrderModifierToProperties(element)
-                focusOrderElement = FocusPropertiesModifier(
-                    focusPropertiesScope = scope,
-                    inspectorInfo = debugInspectorInfo {
-                        name = "focusProperties"
-                        properties["scope"] = scope
-                    }
-                )
-                updateModifierLocalProvider(focusOrderElement!!)
-                if (duringAttach)
-                    updateFocusOrderModifierLocalConsumer()
-                else
-                    sideEffect { updateFocusOrderModifierLocalConsumer() }
-            }
         }
         if (isKind(Nodes.Draw)) {
             if (element is DrawCacheModifier) {
@@ -219,6 +204,9 @@
                 }
             }
         }
+        if (element is FocusRequesterModifier) {
+            element.focusRequester.focusRequesterNodes += this
+        }
         if (isKind(Nodes.PointerInput)) {
             if (element is PointerInputModifier) {
                 element.pointerInputFilter.layoutCoordinates = coordinator
@@ -262,7 +250,6 @@
         invalidateDraw()
     }
 
-    private var focusOrderElement: FocusPropertiesModifier? = null
     private var _providedValues: BackwardsCompatLocalMap? = null
     var readValues = hashSetOf<ModifierLocal<*>>()
     override val providedValues: ModifierLocalMap get() = _providedValues ?: modifierLocalMapOf()
@@ -292,18 +279,7 @@
         }
     }
 
-    fun updateFocusOrderModifierLocalConsumer() {
-        if (isAttached) {
-            requireOwner().snapshotObserver.observeReads(
-                this,
-                updateFocusOrderModifierLocalConsumer
-            ) {
-                (focusOrderElement!! as ModifierLocalConsumer).onModifierLocalsUpdated(this)
-            }
-        }
-    }
-
-    fun updateModifierLocalProvider(element: ModifierLocalProvider<*>) {
+    private fun updateModifierLocalProvider(element: ModifierLocalProvider<*>) {
         val providedValues = _providedValues
         if (providedValues != null && providedValues.contains(element.key)) {
             providedValues.element = element
@@ -314,7 +290,7 @@
             _providedValues = BackwardsCompatLocalMap(element)
             // we only need to notify the modifierLocalManager of an inserted provider
             // in the cases where a provider was added to the chain where it was possible
-            // that consumers below it could need to be invalidated. If this layoutnode
+            // that consumers below it could need to be invalidated. If this layout node
             // is just now being created, then that is impossible. In this case, we can just
             // do nothing and wait for the child consumers to read us. We infer this by
             // checking to see if the tail node is attached or not. If it is not, then the node
@@ -448,6 +424,18 @@
         }
     }
 
+    override fun onFocusEvent(focusState: FocusState) {
+        val focusEventModifier = element
+        check(focusEventModifier is FocusEventModifier)
+        focusEventModifier.onFocusEvent(focusState)
+    }
+
+    override fun modifyFocusProperties(focusProperties: FocusProperties) {
+        val focusOrderModifier = element
+        check(focusOrderModifier is FocusOrderModifier)
+        focusProperties.apply(FocusOrderModifierToProperties(focusOrderModifier))
+    }
+
     override fun toString(): String = element.toString()
 }
 
@@ -463,7 +451,3 @@
 private val updateModifierLocalConsumer = { it: BackwardsCompatNode ->
     it.updateModifierLocalConsumer()
 }
-
-private val updateFocusOrderModifierLocalConsumer = { it: BackwardsCompatNode ->
-    it.updateFocusOrderModifierLocalConsumer()
-}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
index 2b4b7ac..ea2231a7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
@@ -94,6 +94,29 @@
 }
 
 @ExperimentalComposeUiApi
+internal fun DelegatableNode.ancestors(mask: Int): List<Modifier.Node>? {
+    check(node.isAttached)
+    var ancestors: MutableList<Modifier.Node>? = null
+    var node: Modifier.Node? = node.parent
+    var layout: LayoutNode? = requireLayoutNode()
+    while (layout != null) {
+        val head = layout.nodes.head
+        if (head.aggregateChildKindSet and mask != 0) {
+            while (node != null) {
+                if (node.kindSet and mask != 0) {
+                    if (ancestors == null) ancestors = mutableListOf()
+                    ancestors += node
+                }
+                node = node.parent
+            }
+        }
+        layout = layout.parent
+        node = layout?.nodes?.tail
+    }
+    return ancestors
+}
+
+@ExperimentalComposeUiApi
 internal fun DelegatableNode.nearestAncestor(mask: Int): Modifier.Node? {
     check(node.isAttached)
     var node: Modifier.Node? = node.parent
@@ -115,6 +138,33 @@
 }
 
 @ExperimentalComposeUiApi
+internal fun DelegatableNode.firstChild(mask: Int): Modifier.Node? {
+    check(node.isAttached)
+    val branches = mutableVectorOf<Modifier.Node>()
+    val child = node.child
+    if (child == null)
+        branches.addLayoutNodeChildren(node)
+    else
+        branches.add(child)
+    while (branches.isNotEmpty()) {
+        val branch = branches.removeAt(branches.lastIndex)
+        if (branch.aggregateChildKindSet and mask == 0) {
+            branches.addLayoutNodeChildren(branch)
+            // none of these nodes match the mask, so don't bother traversing them
+            continue
+        }
+        var node: Modifier.Node? = branch
+        while (node != null) {
+            if (node.kindSet and mask != 0) {
+                return node
+            }
+            node = node.child
+        }
+    }
+    return null
+}
+
+@ExperimentalComposeUiApi
 internal inline fun DelegatableNode.visitSubtree(mask: Int, block: (Modifier.Node) -> Unit) {
     // TODO(lmr): we might want to add some safety wheels to prevent this from being called
     //  while one of the chains is being diffed / updated.
@@ -263,11 +313,21 @@
     block: (T) -> Unit
 ) = visitAncestors(type.mask) { if (it is T) block(it) }
 
+@Suppress("UNCHECKED_CAST") // Type info lost due to erasure.
+@ExperimentalComposeUiApi
+internal inline fun <reified T> DelegatableNode.ancestors(
+    type: NodeKind<T>
+): List<T>? = ancestors(type.mask) as? List<T>
+
 @ExperimentalComposeUiApi
 internal inline fun <reified T : Any> DelegatableNode.nearestAncestor(type: NodeKind<T>): T? =
     nearestAncestor(type.mask) as? T
 
 @ExperimentalComposeUiApi
+internal inline fun <reified T : Any> DelegatableNode.firstChild(type: NodeKind<T>): T? =
+    firstChild(type.mask) as? T
+
+@ExperimentalComposeUiApi
 internal inline fun <reified T> DelegatableNode.visitSubtree(
     type: NodeKind<T>,
     block: (T) -> Unit
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index a2f0c22..0674ea8 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusTargetModifierNode
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.input.pointer.PointerInputFilter
@@ -41,6 +42,9 @@
 import androidx.compose.ui.node.LayoutNode.LayoutState.Measuring
 import androidx.compose.ui.node.LayoutNode.LayoutState.LookaheadLayingOut
 import androidx.compose.ui.node.LayoutNode.LayoutState.LookaheadMeasuring
+import androidx.compose.ui.node.Nodes.FocusEvent
+import androidx.compose.ui.node.Nodes.FocusProperties
+import androidx.compose.ui.node.Nodes.FocusTarget
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.simpleIdentityToString
 import androidx.compose.ui.semantics.SemanticsModifierCore.Companion.generateSemanticsId
@@ -398,6 +402,8 @@
 
         forEachCoordinatorIncludingInner { it.attach() }
         onAttach?.invoke(owner)
+
+        invalidateFocusOnAttach()
     }
 
     /**
@@ -410,6 +416,7 @@
         checkNotNull(owner) {
             "Cannot detach node that is already detached!  Tree: " + parent?.debugTreeToString()
         }
+        invalidateFocusOnDetach()
         val parent = this.parent
         if (parent != null) {
             parent.invalidateLayer()
@@ -1044,6 +1051,33 @@
         }
     }
 
+    @OptIn(ExperimentalComposeUiApi::class)
+    private fun invalidateFocusOnAttach() {
+        if (nodes.has(FocusTarget or FocusProperties or FocusEvent)) {
+            nodes.headToTail {
+                if (it.isKind(FocusTarget) or it.isKind(FocusProperties) or it.isKind(FocusEvent)) {
+                    autoInvalidateInsertedNode(it)
+                }
+            }
+        }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    private fun invalidateFocusOnDetach() {
+        if (nodes.has(FocusTarget)) {
+            nodes.tailToHead {
+                if (
+                    it.isKind(FocusTarget) &&
+                    it is FocusTargetModifierNode &&
+                    it.focusState.isFocused
+                ) {
+                    requireOwner().focusOwner.clearFocus(force = true, refreshFocusEvents = false)
+                    it.scheduleInvalidationForFocusEvents()
+                }
+            }
+        }
+    }
+
     internal inline fun ignoreRemeasureRequests(block: () -> Unit) {
         ignoreRemeasureRequests = true
         block()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
index c56b027..f3603db 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
@@ -468,12 +468,11 @@
         element: Modifier.Element,
         child: Modifier.Node,
     ): Modifier.Node {
-        val node = if (element is ModifierNodeElement<*>) {
-            element.create().also {
+        val node = when (element) {
+            is ModifierNodeElement<*> -> element.create().also {
                 it.kindSet = calculateNodeKindSetFrom(it)
             }
-        } else {
-            BackwardsCompatNode(element)
+            else -> BackwardsCompatNode(element)
         }
         return insertParent(node, child)
     }
@@ -506,29 +505,34 @@
         next: Modifier.Element,
         node: Modifier.Node,
     ): Modifier.Node {
-        if (prev !is ModifierNodeElement<*> || next !is ModifierNodeElement<*>) {
-            check(node is BackwardsCompatNode)
-            node.element = next
-            // we always autoInvalidate BackwardsCompatNode
-            autoInvalidateUpdatedNode(node)
-            return node
-        }
-        val updated = next.updateUnsafe(node)
-        if (updated !== node) {
-            // if a new instance is returned, we want to detach the old one
-            autoInvalidateRemovedNode(node)
-            node.detach()
-            val result = replaceNode(node, updated)
-            autoInvalidateInsertedNode(updated)
-            return result
-        } else {
-            // the node was updated. we are done.
-            if (next.autoInvalidate) {
-                // the modifier element is labeled as "auto invalidate", which means that since the
-                // node was updated, we need to invalidate everything relevant to it
-                autoInvalidateUpdatedNode(updated)
+        when {
+            prev is ModifierNodeElement<*> && next is ModifierNodeElement<*> -> {
+                val updated = next.updateUnsafe(node)
+                if (updated !== node) {
+                    // if a new instance is returned, we want to detach the old one
+                    autoInvalidateRemovedNode(node)
+                    node.detach()
+                    val result = replaceNode(node, updated)
+                    autoInvalidateInsertedNode(updated)
+                    return result
+                } else {
+                    // the node was updated. we are done.
+                    if (next.autoInvalidate) {
+                        // the modifier element is labeled as "auto invalidate", which means
+                        // that since the node was updated, we need to invalidate everything
+                        // relevant to it.
+                        autoInvalidateUpdatedNode(updated)
+                    }
+                    return updated
+                }
             }
-            return updated
+            node is BackwardsCompatNode -> {
+                node.element = next
+                // We always autoInvalidate BackwardsCompatNode.
+                autoInvalidateUpdatedNode(node)
+                return node
+            }
+            else -> error("Unknown Modifier.Node type")
         }
     }
 
@@ -622,6 +626,8 @@
 
     internal fun has(type: NodeKind<*>): Boolean = aggregateChildKindSet and type.mask != 0
 
+    internal fun has(mask: Int): Boolean = aggregateChildKindSet and mask != 0
+
     override fun toString(): String = buildString {
         append("[")
         if (head === tail) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
index 4b257cf..d0bb5e4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
@@ -21,8 +21,16 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.DrawModifier
+import androidx.compose.ui.focus.FocusEventModifier
+import androidx.compose.ui.focus.FocusEventModifierNode
 import androidx.compose.ui.focus.FocusOrderModifier
+import androidx.compose.ui.focus.FocusProperties
+import androidx.compose.ui.focus.FocusPropertiesModifierNode
+import androidx.compose.ui.focus.FocusTargetModifierNode
+import androidx.compose.ui.focus.scheduleInvalidationOfAssociatedFocusTargets
+import androidx.compose.ui.input.key.KeyInputModifierNode
 import androidx.compose.ui.input.pointer.PointerInputModifier
+import androidx.compose.ui.input.rotary.RotaryInputModifierNode
 import androidx.compose.ui.layout.IntermediateLayoutModifier
 import androidx.compose.ui.layout.LayoutModifier
 import androidx.compose.ui.layout.LookaheadOnPlacedModifier
@@ -40,6 +48,7 @@
     inline infix fun or(other: NodeKind<*>): Int = mask or other.mask
     inline infix fun or(other: Int): Int = mask or other
 }
+
 internal inline infix fun Int.or(other: NodeKind<*>): Int = this or other.mask
 
 // For a given NodeCoordinator, the "LayoutAware" nodes that it is concerned with should include
@@ -47,9 +56,8 @@
 // implements any other node interfaces, such as draw, those should be visited by the coordinator
 // below them.
 @OptIn(ExperimentalComposeUiApi::class)
-internal val NodeKind<*>.includeSelfInTraversal: Boolean get() {
-    return mask and Nodes.LayoutAware.mask != 0
-}
+internal val NodeKind<*>.includeSelfInTraversal: Boolean
+    get() = mask and Nodes.LayoutAware.mask != 0
 
 // Note that these don't inherit from Modifier.Node to allow for a single Modifier.Node
 // instance to implement multiple Node interfaces
@@ -76,6 +84,16 @@
     inline val GlobalPositionAware get() = NodeKind<GlobalPositionAwareModifierNode>(0b1 shl 8)
     @JvmStatic
     inline val IntermediateMeasure get() = NodeKind<IntermediateLayoutModifierNode>(0b1 shl 9)
+    @JvmStatic
+    inline val FocusTarget get() = NodeKind<FocusTargetModifierNode>(0b1 shl 10)
+    @JvmStatic
+    inline val FocusProperties get() = NodeKind<FocusPropertiesModifierNode>(0b1 shl 11)
+    @JvmStatic
+    inline val FocusEvent get() = NodeKind<FocusEventModifierNode>(0b1 shl 12)
+    @JvmStatic
+    inline val KeyInput get() = NodeKind<KeyInputModifierNode>(0b1 shl 13)
+    @JvmStatic
+    inline val RotaryInput get() = NodeKind<RotaryInputModifierNode>(0b1 shl 14)
     // ...
 }
 
@@ -85,7 +103,6 @@
     if (element is LayoutModifier) {
         mask = mask or Nodes.Layout
     }
-    @OptIn(ExperimentalComposeUiApi::class)
     if (element is IntermediateLayoutModifier) {
         mask = mask or Nodes.IntermediateMeasure
     }
@@ -100,13 +117,16 @@
     }
     if (
         element is ModifierLocalConsumer ||
-        element is ModifierLocalProvider<*> ||
-        // Special handling for FocusOrderModifier -- we have to use modifier local
-        // consumers and providers for it.
-        element is FocusOrderModifier
+        element is ModifierLocalProvider<*>
     ) {
         mask = mask or Nodes.Locals
     }
+    if (element is FocusEventModifier) {
+        mask = mask or Nodes.FocusEvent
+    }
+    if (element is FocusOrderModifier) {
+        mask = mask or Nodes.FocusProperties
+    }
     if (element is OnGloballyPositionedModifier) {
         mask = mask or Nodes.GlobalPositionAware
     }
@@ -153,6 +173,21 @@
     if (node is IntermediateLayoutModifierNode) {
         mask = mask or Nodes.IntermediateMeasure
     }
+    if (node is FocusTargetModifierNode) {
+        mask = mask or Nodes.FocusTarget
+    }
+    if (node is FocusPropertiesModifierNode) {
+        mask = mask or Nodes.FocusProperties
+    }
+    if (node is FocusEventModifierNode) {
+        mask = mask or Nodes.FocusEvent
+    }
+    if (node is KeyInputModifierNode) {
+        mask = mask or Nodes.KeyInput
+    }
+    if (node is RotaryInputModifierNode) {
+        mask = mask or Nodes.RotaryInput
+    }
     return mask
 }
 
@@ -168,6 +203,7 @@
 
 @OptIn(ExperimentalComposeUiApi::class)
 internal fun autoInvalidateUpdatedNode(node: Modifier.Node) = autoInvalidateNode(node, Updated)
+
 @OptIn(ExperimentalComposeUiApi::class)
 private fun autoInvalidateNode(node: Modifier.Node, phase: Int) {
     if (node.isKind(Nodes.Layout) && node is LayoutModifierNode) {
@@ -189,4 +225,48 @@
     if (node.isKind(Nodes.ParentData) && node is ParentDataModifierNode) {
         node.invalidateParentData()
     }
+    if (node.isKind(Nodes.FocusTarget) && node is FocusTargetModifierNode) {
+        when (phase) {
+            Removed -> node.onRemoved()
+            else -> node.requireOwner().focusOwner.scheduleInvalidation(node)
+        }
+    }
+    if (
+        node.isKind(Nodes.FocusProperties) &&
+        node is FocusPropertiesModifierNode &&
+        node.specifiesCanFocusProperty()
+    ) {
+        when (phase) {
+            Removed -> node.scheduleInvalidationOfAssociatedFocusTargets()
+            else -> node.requireOwner().focusOwner.scheduleInvalidation(node)
+        }
+    }
+    if (node.isKind(Nodes.FocusEvent) && node is FocusEventModifierNode && phase != Removed) {
+        node.requireOwner().focusOwner.scheduleInvalidation(node)
+    }
+}
+
+/**
+ * This function checks if the FocusProperties node has set the canFocus [FocusProperties.canFocus]
+ * property.
+ *
+ * We use a singleton CanFocusChecker to prevent extra allocations, and in doing so, we assume that
+ * there won't be multiple concurrent calls of this function. This is not an issue since this is
+ * called from the main thread, but if this changes in the future, replace the
+ * [CanFocusChecker.reset] call with a new [FocusProperties] object for every invocation.
+ */
+@ExperimentalComposeUiApi
+private fun FocusPropertiesModifierNode.specifiesCanFocusProperty(): Boolean {
+    CanFocusChecker.reset()
+    modifyFocusProperties(CanFocusChecker)
+    return CanFocusChecker.isCanFocusSet()
+}
+
+private object CanFocusChecker : FocusProperties {
+    private var canFocusValue: Boolean? = null
+    override var canFocus: Boolean
+        get() = checkNotNull(canFocusValue)
+        set(value) { canFocusValue = value }
+    fun isCanFocusSet(): Boolean = canFocusValue != null
+    fun reset() { canFocusValue = null }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
index 4b1abd0..9a7c79c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
@@ -20,7 +20,7 @@
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
 import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusOwner
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.hapticfeedback.HapticFeedback
@@ -112,9 +112,9 @@
     val pointerIconService: PointerIconService
 
     /**
-     * Provide a focus manager that controls focus within Compose.
+     * Provide a focus owner that controls focus within Compose.
      */
-    val focusManager: FocusManager
+    val focusOwner: FocusOwner
 
     /**
      * Provide information about the window that hosts this [Owner].
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt
index 3fd7321..158a357 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/CompositionLocals.kt
@@ -180,7 +180,7 @@
         LocalAutofillTree provides owner.autofillTree,
         LocalClipboardManager provides owner.clipboardManager,
         LocalDensity provides owner.density,
-        LocalFocusManager provides owner.focusManager,
+        LocalFocusManager provides owner.focusOwner,
         @Suppress("DEPRECATION") LocalFontLoader
             providesDefault @Suppress("DEPRECATION") owner.fontLoader,
         LocalFontFamilyResolver providesDefault owner.fontFamilyResolver,
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt
index 15586dd5..437c083 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.ui.platform
 
-import androidx.compose.ui.input.key.KeyInputModifier
+import androidx.compose.ui.focus.FocusOwner
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType
 import androidx.compose.ui.input.key.type
@@ -26,7 +26,7 @@
 
 internal actual fun sendKeyEvent(
     platformInputService: PlatformInput,
-    keyInputModifier: KeyInputModifier,
+    focusOwner: FocusOwner,
     keyEvent: KeyEvent
 ): Boolean {
     when {
@@ -35,8 +35,7 @@
         keyEvent.type == KeyEventType.KeyUp ->
             platformInputService.charKeyPressed = false
     }
-
-    return keyInputModifier.processKeyInput(keyEvent)
+    return focusOwner.dispatchKeyEvent(keyEvent)
 }
 
 private val defaultCursor = Cursor(Cursor.DEFAULT_CURSOR)
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
index 373368b..8114dfc 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.DefaultPointerButtons
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.InternalComposeUiApi
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.PrimaryPressedPointerButtons
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
@@ -33,8 +34,8 @@
 import androidx.compose.ui.focus.FocusDirection.Companion.Next
 import androidx.compose.ui.focus.FocusDirection.Companion.Out
 import androidx.compose.ui.focus.FocusDirection.Companion.Previous
-import androidx.compose.ui.focus.FocusManager
-import androidx.compose.ui.focus.FocusManagerImpl
+import androidx.compose.ui.focus.FocusOwner
+import androidx.compose.ui.focus.FocusOwnerImpl
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.asComposeCanvas
@@ -46,9 +47,10 @@
 import androidx.compose.ui.input.key.Key.Companion.Tab
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
-import androidx.compose.ui.input.key.KeyInputModifier
 import androidx.compose.ui.input.key.isShiftPressed
 import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.onPreviewKeyEvent
 import androidx.compose.ui.input.key.type
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerIcon
@@ -120,12 +122,12 @@
         properties = {}
     )
 
-    private val _focusManager: FocusManagerImpl = FocusManagerImpl().apply {
+    override val focusOwner: FocusOwner = FocusOwnerImpl {
+        registerOnEndApplyChangesListener(it)
+    }.apply {
         // TODO(demin): support RTL [onRtlPropertiesChanged]
         layoutDirection = LayoutDirection.Ltr
     }
-    override val focusManager: FocusManager
-        get() = _focusManager
 
     // TODO: Set the input mode. For now we don't support touch mode, (always in Key mode).
     private val _inputModeManager = InputModeManagerImpl(
@@ -148,16 +150,13 @@
 
     // TODO(b/177931787) : Consider creating a KeyInputManager like we have for FocusManager so
     //  that this common logic can be used by all owners.
-    private val keyInputModifier: KeyInputModifier = KeyInputModifier(
-        >
-            val focusDirection = getFocusDirection(it)
-            if (focusDirection == null || it.type != KeyDown) return@KeyInputModifier false
+    private val keyInputModifier = Modifier.onKeyEvent {
+        val focusDirection = getFocusDirection(it)
+        if (focusDirection == null || it.type != KeyDown) return@onKeyEvent false
 
-            // Consume the key event if we moved focus.
-            focusManager.moveFocus(focusDirection)
-        },
-        >
-    )
+        // Consume the key event if we moved focus.
+        focusOwner.moveFocus(focusDirection)
+    }
 
     @Suppress("unused") // to be used in JB fork (not all prerequisite changes added yet)
     internal fun setCurrentKeyboardModifiers(modifiers: PointerKeyboardModifiers) {
@@ -179,14 +178,10 @@
     override val root = LayoutNode().also {
         it.measurePolicy = RootMeasurePolicy
         it.modifier = semanticsModifier
-            .then(_focusManager.modifier)
+            .then(focusOwner.modifier)
             .then(keyInputModifier)
-            .then(
-                KeyInputModifier(
-                    >
-                    >
-                )
-            )
+            .onPreviewKeyEvent(onPreviewKeyEvent)
+            .onKeyEvent(onKeyEvent)
     }
 
     override val rootForTest = this
@@ -202,7 +197,7 @@
     init {
         snapshotObserver.startObserving()
         root.attach(this)
-        _focusManager.takeFocus()
+        focusOwner.takeFocus()
     }
 
     fun dispose() {
@@ -237,7 +232,7 @@
     override val viewConfiguration: ViewConfiguration = DefaultViewConfiguration(density)
 
     override fun sendKeyEvent(keyEvent: KeyEvent): Boolean =
-        sendKeyEvent(platformInputService, keyInputModifier, keyEvent)
+        sendKeyEvent(platformInputService, focusOwner, keyEvent)
 
     override var showLayoutBounds = false
 
@@ -507,7 +502,7 @@
 
 internal expect fun sendKeyEvent(
     platformInputService: PlatformInput,
-    keyInputModifier: KeyInputModifier,
+    focusOwner: FocusOwner,
     keyEvent: KeyEvent
 ): Boolean
 
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt
deleted file mode 100644
index 88e33de..0000000
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt
+++ /dev/null
@@ -1,174 +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.compose.ui.focus
-
-import androidx.compose.ui.focus.FocusStateImpl.Active
-import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
-import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Deactivated
-import androidx.compose.ui.focus.FocusStateImpl.DeactivatedParent
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
-import androidx.compose.ui.node.InnerNodeCoordinator
-import androidx.compose.ui.node.LayoutNode
-import androidx.compose.ui.node.add
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-class FocusManagerTest(private val initialFocusState: FocusState) {
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "rootInitialFocus = {0}")
-        fun initParameters(): List<FocusState> = FocusStateImpl.values().asList()
-    }
-
-    private val focusModifier = FocusModifier(Inactive)
-    private val focusManager = FocusManagerImpl(focusModifier)
-
-    @Before
-    fun setup() {
-        val innerPlaceable = InnerNodeCoordinator(LayoutNode())
-        focusModifier.coordinator = innerPlaceable
-    }
-
-    @Test
-    fun defaultFocusState() {
-        assertThat(focusModifier.focusState).isEqualTo(Inactive)
-    }
-
-    @Test
-    fun takeFocus_onlyInactiveChangesState() {
-        // Arrange.
-        focusModifier.focusState = initialFocusState as FocusStateImpl
-
-        // Act.
-        focusManager.takeFocus()
-
-        // Assert.
-        assertThat(focusModifier.focusState).isEqualTo(
-            when (initialFocusState) {
-                Inactive -> Active
-                Active, ActiveParent, Captured, Deactivated, DeactivatedParent -> initialFocusState
-            }
-        )
-    }
-
-    @Test
-    fun releaseFocus_changesStateToInactive() {
-        // Arrange.
-        focusModifier.focusState = initialFocusState as FocusStateImpl
-        if (initialFocusState == ActiveParent || initialFocusState == DeactivatedParent) {
-            val childLayoutNode = LayoutNode()
-            val child = FocusModifier(Active).apply {
-                coordinator = InnerNodeCoordinator(childLayoutNode)
-            }
-            focusModifier.coordinator!!.layoutNode.add(childLayoutNode)
-            focusModifier.focusedChild = child
-        }
-
-        // Act.
-        focusManager.releaseFocus()
-
-        // Assert.
-        assertThat(focusModifier.focusState).isEqualTo(
-            when (initialFocusState) {
-                Active, ActiveParent, Captured, Inactive -> Inactive
-                Deactivated, DeactivatedParent -> Deactivated
-            }
-        )
-    }
-
-    @Test
-    fun clearFocus_forced() {
-        // Arrange.
-        focusModifier.focusState = initialFocusState as FocusStateImpl
-        if (initialFocusState == ActiveParent || initialFocusState == DeactivatedParent) {
-            val childLayoutNode = LayoutNode()
-            val child = FocusModifier(Active).apply {
-                coordinator = InnerNodeCoordinator(childLayoutNode)
-            }
-            focusModifier.coordinator!!.layoutNode.add(childLayoutNode)
-            focusModifier.focusedChild = child
-        }
-
-        // Act.
-        focusManager.clearFocus(force = true)
-
-        // Assert.
-        assertThat(focusModifier.focusState).isEqualTo(
-            when (initialFocusState) {
-                // If the initial state was focused, assert that after clearing the hierarchy,
-                // the root is set to Active.
-                Active, ActiveParent, Captured -> Active
-                Deactivated, DeactivatedParent -> Deactivated
-                Inactive -> Inactive
-            }
-        )
-    }
-
-    @Test
-    fun clearFocus_notForced() {
-        // Arrange.
-        focusModifier.focusState = initialFocusState as FocusStateImpl
-        if (initialFocusState == ActiveParent || initialFocusState == DeactivatedParent) {
-            val childLayoutNode = LayoutNode()
-            val child = FocusModifier(Active).apply {
-                coordinator = InnerNodeCoordinator(childLayoutNode)
-            }
-            focusModifier.coordinator!!.layoutNode.add(childLayoutNode)
-            focusModifier.focusedChild = child
-        }
-
-        // Act.
-        focusManager.clearFocus(force = false)
-
-        // Assert.
-        assertThat(focusModifier.focusState).isEqualTo(
-            when (initialFocusState) {
-                // If the initial state was focused, assert that after clearing the hierarchy,
-                // the root is set to Active.
-                Active, ActiveParent -> Active
-                Deactivated, DeactivatedParent -> Deactivated
-                Captured -> Captured
-                Inactive -> Inactive
-            }
-        )
-    }
-
-    @Test
-    fun clearFocus_childIsCaptured() {
-        if (initialFocusState == ActiveParent || initialFocusState == DeactivatedParent) {
-            // Arrange.
-            focusModifier.focusState = initialFocusState as FocusStateImpl
-            val childLayoutNode = LayoutNode()
-            val child = FocusModifier(Captured).apply {
-                coordinator = InnerNodeCoordinator(childLayoutNode)
-            }
-            focusModifier.coordinator!!.layoutNode.add(childLayoutNode)
-            focusModifier.focusedChild = child
-
-            // Act.
-            focusManager.clearFocus()
-
-            // Assert.
-            assertThat(focusModifier.focusState).isEqualTo(initialFocusState)
-        }
-    }
-}
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index 0d2d1c1..c957ee8 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -23,7 +23,7 @@
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusOwner
 import androidx.compose.ui.geometry.MutableRect
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
@@ -2504,7 +2504,7 @@
         get() = TODO("Not yet implemented")
     override val pointerIconService: PointerIconService
         get() = TODO("Not yet implemented")
-    override val focusManager: FocusManager
+    override val focusOwner: FocusOwner
         get() = TODO("Not yet implemented")
     override val windowInfo: WindowInfo
         get() = TODO("Not yet implemented")
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt
index 0358c58..84732ff 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt
@@ -25,7 +25,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
-import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusOwner
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.hapticfeedback.HapticFeedback
@@ -340,7 +340,7 @@
             get() = TODO("Not yet implemented")
         override val pointerIconService: PointerIconService
             get() = TODO("Not yet implemented")
-        override val focusManager: FocusManager
+        override val focusOwner: FocusOwner
             get() = TODO("Not yet implemented")
         override val windowInfo: WindowInfo
             get() = TODO("Not yet implemented")