[go: nahoru, domu]

Merge "ui-desktop: different redrawing strategy with DesktopUIDispatcher" into androidx-master-dev
diff --git a/playground-common/CONTRIBUTING.md b/CONTRIBUTING.md
similarity index 100%
rename from playground-common/CONTRIBUTING.md
rename to CONTRIBUTING.md
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index c1ac1ed..426f9b2 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -118,7 +118,7 @@
     val VIEWPAGER2 = Version("1.1.0-alpha02")
     val WEAR = Version("1.2.0-alpha01")
     val WEAR_INPUT = Version("1.0.0-alpha01")
-    val WEBKIT = Version("1.3.0-rc01")
+    val WEBKIT = Version("1.4.0-alpha01")
     val WINDOW = Version("1.0.0-alpha02")
     val WINDOW_SIDECAR = Version("0.1.0-alpha01")
     val WORK = Version("2.4.0-rc01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 6e30585..2e48035 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -29,10 +29,10 @@
  */
 val RELEASE_RULE = docsRules("public", false) {
     ignore(LibraryGroups.ACTIVITY.group, "activity-lint")
-    prebuilts(LibraryGroups.ACTIVITY, "1.2.0-alpha06")
+    prebuilts(LibraryGroups.ACTIVITY, "1.2.0-alpha07")
     prebuilts(LibraryGroups.ADS, "1.0.0-alpha04")
     prebuilts(LibraryGroups.ANNOTATION, "annotation", "1.2.0-alpha01")
-    prebuilts(LibraryGroups.ANNOTATION, "annotation-experimental", "1.0.0")
+    prebuilts(LibraryGroups.ANNOTATION, "annotation-experimental", "1.1.0-alpha01")
     prebuilts(LibraryGroups.ANNOTATION, "annotation-experimental-lint", "1.0.0")
     ignore(LibraryGroups.APPCOMPAT.group, "appcompat-lint")
     prebuilts(LibraryGroups.APPCOMPAT, "1.3.0-alpha01")
@@ -48,13 +48,13 @@
     ignore(LibraryGroups.CAMERA.group, "camera-testing")
     ignore(LibraryGroups.CAMERA.group, "camera-extensions-stub")
     ignore(LibraryGroups.CAMERA.group, "camera-testlib-extensions")
-    prebuilts(LibraryGroups.CAMERA, "camera-view", "1.0.0-alpha13")
-    prebuilts(LibraryGroups.CAMERA, "camera-extensions", "1.0.0-alpha13")
+    prebuilts(LibraryGroups.CAMERA, "camera-view", "1.0.0-alpha14")
+    prebuilts(LibraryGroups.CAMERA, "camera-extensions", "1.0.0-alpha14")
             .addStubs("camera/camera-extensions-stub/camera-extensions-stub.jar")
-    prebuilts(LibraryGroups.CAMERA, "1.0.0-beta06")
+    prebuilts(LibraryGroups.CAMERA, "1.0.0-beta07")
     prebuilts(LibraryGroups.CARDVIEW, "1.0.0")
     prebuilts(LibraryGroups.COLLECTION, "1.1.0")
-    prebuilts(LibraryGroups.CONCURRENT, "1.1.0-beta01")
+    prebuilts(LibraryGroups.CONCURRENT, "1.1.0-rc01")
     prebuilts(LibraryGroups.CONTENTPAGER, "1.0.0")
     prebuilts(LibraryGroups.COORDINATORLAYOUT, "1.1.0")
     prebuilts(LibraryGroups.CORE, "core", "1.5.0-alpha01")
@@ -74,10 +74,10 @@
     ignore(LibraryGroups.FRAGMENT.group, "fragment-lint")
     ignore(LibraryGroups.FRAGMENT.group, "fragment-testing-lint")
     ignore(LibraryGroups.FRAGMENT.group, "fragment-truth")
-    prebuilts(LibraryGroups.FRAGMENT, "1.3.0-alpha06")
+    prebuilts(LibraryGroups.FRAGMENT, "1.3.0-alpha07")
     prebuilts(LibraryGroups.GRIDLAYOUT, "1.0.0")
     prebuilts(LibraryGroups.HEIFWRITER, "1.1.0-alpha01")
-    prebuilts(LibraryGroups.HILT, "1.0.0-alpha01")
+    prebuilts(LibraryGroups.HILT, "1.0.0-alpha02")
     prebuilts(LibraryGroups.INTERPOLATOR, "1.0.0")
     prebuilts(LibraryGroups.LEANBACK, "1.1.0-alpha03")
     prebuilts(LibraryGroups.LEGACY, "1.0.0")
@@ -87,7 +87,7 @@
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-runtime-ktx-lint")
     prebuilts(LibraryGroups.LIFECYCLE, "lifecycle-extensions", "2.2.0") // No longer published
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-runtime-testing")
-    prebuilts(LibraryGroups.LIFECYCLE, "2.3.0-alpha05")
+    prebuilts(LibraryGroups.LIFECYCLE, "2.3.0-alpha06")
     ignore(LibraryGroups.LOADER.group, "loader-ktx")
     prebuilts(LibraryGroups.LOADER, "1.1.0")
     prebuilts(LibraryGroups.LOCALBROADCASTMANAGER, "1.1.0-alpha01")
@@ -95,7 +95,7 @@
     ignore(LibraryGroups.MEDIA2.group, "media2-exoplayer")
     prebuilts(LibraryGroups.MEDIA2, "media2-widget", "1.1.0-alpha01")
     prebuilts(LibraryGroups.MEDIA2, "1.1.0-alpha01")
-    prebuilts(LibraryGroups.MEDIAROUTER, "1.2.0-alpha01")
+    prebuilts(LibraryGroups.MEDIAROUTER, "1.2.0-alpha02")
     ignore(LibraryGroups.NAVIGATION.group, "navigation-runtime-truth")
     ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-generator")
     ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-gradle-plugin")
@@ -108,9 +108,9 @@
     prebuilts(LibraryGroups.PERCENTLAYOUT, "1.0.1")
     prebuilts(LibraryGroups.PREFERENCE, "preference-ktx", "1.1.1")
     prebuilts(LibraryGroups.PREFERENCE, "1.1.1")
-    prebuilts(LibraryGroups.PRINT, "1.0.0")
+    prebuilts(LibraryGroups.PRINT, "1.1.0-alpha01")
     prebuilts(LibraryGroups.RECOMMENDATION, "1.0.0")
-    prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.2.0-alpha04")
+    prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.2.0-alpha05")
     prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview-selection", "2.0.0-alpha01")
     ignore(LibraryGroups.RECYCLERVIEW.group, "recyclerview-lint")
     prebuilts(LibraryGroups.REMOTECALLBACK, "1.0.0-alpha02")
@@ -119,7 +119,7 @@
     ignore(LibraryGroups.ROOM.group, "room-compiler-xprocessing")
     // TODO: Remove during release phase of rxjava3 artifact
     ignore(LibraryGroups.ROOM.group, "room-rxjava3")
-    prebuilts(LibraryGroups.ROOM, "2.3.0-alpha01")
+    prebuilts(LibraryGroups.ROOM, "2.3.0-alpha02")
     prebuilts(LibraryGroups.SAVEDSTATE, "1.1.0-alpha01")
     // TODO: Remove this ignore once androidx.security:security-biometric:1.0.0-alpha01 is released
     ignore(LibraryGroups.SECURITY.group, "security-biometric")
@@ -139,12 +139,12 @@
     prebuilts(LibraryGroups.SLIDINGPANELAYOUT, "1.1.0")
     ignore(LibraryGroups.INSPECTION_EXTENSIONS.group, "sqlite-inspection")
     prebuilts(LibraryGroups.SQLITE, "2.1.0")
-    prebuilts(LibraryGroups.STARTUP, "1.0.0-alpha01")
-    prebuilts(LibraryGroups.SWIPEREFRESHLAYOUT, "1.1.0")
+    prebuilts(LibraryGroups.STARTUP, "1.0.0-alpha02")
+    prebuilts(LibraryGroups.SWIPEREFRESHLAYOUT, "1.2.0-alpha01")
     prebuilts(LibraryGroups.TEXTCLASSIFIER, "1.0.0-alpha03")
     prebuilts(LibraryGroups.TRACING, "1.0.0-beta01")
     ignore(LibraryGroups.TRANSITION.group, "transition-ktx")
-    prebuilts(LibraryGroups.TRANSITION, "1.4.0-alpha01")
+    prebuilts(LibraryGroups.TRANSITION, "1.4.0-beta01")
     prebuilts(LibraryGroups.TVPROVIDER, "1.0.0")
     prebuilts(LibraryGroups.VECTORDRAWABLE, "vectordrawable", "1.2.0-alpha01")
     prebuilts(LibraryGroups.VECTORDRAWABLE, "vectordrawable-animated", "1.1.0")
@@ -165,7 +165,7 @@
     ignore(LibraryGroups.WORK.group, "work-gcm")
     ignore(LibraryGroups.WORK.group, "work-runtime-lint")
     ignore(LibraryGroups.WORK.group, "work-rxjava3")
-    prebuilts(LibraryGroups.WORK, "2.4.0-rc01")
+    prebuilts(LibraryGroups.WORK, "2.4.0")
     default(Ignore)
 }
 
diff --git a/development/referenceDocs/stageComposeReferenceDocs.sh b/development/referenceDocs/stageComposeReferenceDocs.sh
index d3c1831..b9802ce8 100755
--- a/development/referenceDocs/stageComposeReferenceDocs.sh
+++ b/development/referenceDocs/stageComposeReferenceDocs.sh
@@ -29,7 +29,7 @@
 
 repo sync -j64
 git reset --hard $tipOfTreeSha
-git fetch "https://android.googlesource.com/platform/frameworks/support" refs/changes/23/1215823/4 && git cherry-pick FETCH_HEAD
+git fetch "https://android.googlesource.com/platform/frameworks/support" refs/changes/23/1215823/8 && git cherry-pick FETCH_HEAD
 ./gradlew distTipOfTreeDokkaDocs
 
 printf "============================ STEP 3 =============================== \n"
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
index c1ef8a0..3539a77 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
@@ -21,11 +21,14 @@
 import android.widget.EditText
 import androidx.fragment.app.test.FragmentTestActivity
 import androidx.fragment.test.R
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
 import androidx.test.annotation.UiThreadTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -50,6 +53,50 @@
         instrumentation = InstrumentationRegistry.getInstrumentation()
     }
 
+    // Ensure that a replaced fragment is stopped before its replacement is started
+    // and vice versa when popped
+    @Test
+    fun stopBeforeStart() {
+        if (!FragmentManager.USE_STATE_MANAGER) {
+            return
+        }
+        val fragment1 = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .setReorderingAllowed(true)
+            .commit()
+        activityRule.executePendingTransactions()
+
+        val fragment2 = StrictViewFragment()
+        lateinit var replaceStateWhenStopped: Lifecycle.State
+        lateinit var replaceStateWhenPopStarted: Lifecycle.State
+        instrumentation.runOnMainSync {
+            fragment1.lifecycle.addObserver(LifecycleEventObserver { _, event ->
+                if (event == Lifecycle.Event.ON_STOP) {
+                    replaceStateWhenStopped = fragment2.lifecycle.currentState
+                } else if (event == Lifecycle.Event.ON_START) {
+                    replaceStateWhenPopStarted = fragment2.lifecycle.currentState
+                }
+            })
+        }
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        activityRule.executePendingTransactions()
+
+        assertWithMessage("Fragment1 should be stopped before Fragment2 moves to " +
+                replaceStateWhenStopped)
+            .that(replaceStateWhenStopped).isLessThan(Lifecycle.State.STARTED)
+
+        activityRule.popBackStackImmediate()
+
+        assertWithMessage("Fragment1 should be started only after Fragment2 moves from " +
+                replaceStateWhenPopStarted)
+            .that(replaceStateWhenPopStarted).isLessThan(Lifecycle.State.STARTED)
+    }
+
     // Test that when you add and replace a fragment that only the replace's add
     // actually creates a View.
     @Test
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 18dd954..4c0205f 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -2117,12 +2117,40 @@
         executeOps(records, isRecordPop, startIndex, endIndex);
 
         if (USE_STATE_MANAGER) {
-            if (allowReordering) {
-                moveToState(mCurState, true);
-            }
             // The last operation determines the overall direction, this ensures that operations
             // such as push, push, pop, push are correctly considered a push
             boolean isPop = isRecordPop.get(endIndex - 1);
+            if (allowReordering) {
+                // Ensure that Fragments directly affected by operations
+                // are moved to their expected state in operation order
+                for (int index = startIndex; index < endIndex; index++) {
+                    BackStackRecord record = records.get(index);
+                    if (isPop) {
+                        // Pop operations get applied in reverse order
+                        for (int opIndex = record.mOps.size() - 1; opIndex >= 0; opIndex--) {
+                            FragmentTransaction.Op op = record.mOps.get(opIndex);
+                            Fragment fragment = op.mFragment;
+                            if (fragment != null) {
+                                FragmentStateManager fragmentStateManager =
+                                        createOrGetFragmentStateManager(fragment);
+                                fragmentStateManager.moveToExpectedState();
+                            }
+                        }
+                    } else {
+                        for (FragmentTransaction.Op op : record.mOps) {
+                            Fragment fragment = op.mFragment;
+                            if (fragment != null) {
+                                FragmentStateManager fragmentStateManager =
+                                        createOrGetFragmentStateManager(fragment);
+                                fragmentStateManager.moveToExpectedState();
+                            }
+                        }
+                    }
+
+                }
+                // And only then do we move all other fragments to the current state
+                moveToState(mCurState, true);
+            }
             Set<SpecialEffectsController> changedControllers = collectChangedControllers(
                     records, startIndex, endIndex);
             for (SpecialEffectsController controller : changedControllers) {
diff --git a/ui/ui-core/api/0.1.0-dev16.txt b/ui/ui-core/api/0.1.0-dev16.txt
index e8262d5..2673614 100644
--- a/ui/ui-core/api/0.1.0-dev16.txt
+++ b/ui/ui-core/api/0.1.0-dev16.txt
@@ -1981,6 +1981,7 @@
     method public int getId();
     method public boolean getMergingEnabled();
     method public androidx.ui.core.semantics.SemanticsNode? getParent();
+    method public androidx.ui.geometry.Offset getPositionInRoot();
     method public androidx.ui.unit.IntSize getSize();
     method public boolean isRoot();
     property public final androidx.ui.unit.PxBounds boundsInRoot;
@@ -1992,6 +1993,7 @@
     property public final int id;
     property public final boolean isRoot;
     property public final androidx.ui.core.semantics.SemanticsNode? parent;
+    property public final androidx.ui.geometry.Offset positionInRoot;
     property public final androidx.ui.unit.IntSize size;
   }
 
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index e8262d5..2673614 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -1981,6 +1981,7 @@
     method public int getId();
     method public boolean getMergingEnabled();
     method public androidx.ui.core.semantics.SemanticsNode? getParent();
+    method public androidx.ui.geometry.Offset getPositionInRoot();
     method public androidx.ui.unit.IntSize getSize();
     method public boolean isRoot();
     property public final androidx.ui.unit.PxBounds boundsInRoot;
@@ -1992,6 +1993,7 @@
     property public final int id;
     property public final boolean isRoot;
     property public final androidx.ui.core.semantics.SemanticsNode? parent;
+    property public final androidx.ui.geometry.Offset positionInRoot;
     property public final androidx.ui.unit.IntSize size;
   }
 
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt
index e8262d5..2673614 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev16.txt
@@ -1981,6 +1981,7 @@
     method public int getId();
     method public boolean getMergingEnabled();
     method public androidx.ui.core.semantics.SemanticsNode? getParent();
+    method public androidx.ui.geometry.Offset getPositionInRoot();
     method public androidx.ui.unit.IntSize getSize();
     method public boolean isRoot();
     property public final androidx.ui.unit.PxBounds boundsInRoot;
@@ -1992,6 +1993,7 @@
     property public final int id;
     property public final boolean isRoot;
     property public final androidx.ui.core.semantics.SemanticsNode? parent;
+    property public final androidx.ui.geometry.Offset positionInRoot;
     property public final androidx.ui.unit.IntSize size;
   }
 
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index e8262d5..2673614 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -1981,6 +1981,7 @@
     method public int getId();
     method public boolean getMergingEnabled();
     method public androidx.ui.core.semantics.SemanticsNode? getParent();
+    method public androidx.ui.geometry.Offset getPositionInRoot();
     method public androidx.ui.unit.IntSize getSize();
     method public boolean isRoot();
     property public final androidx.ui.unit.PxBounds boundsInRoot;
@@ -1992,6 +1993,7 @@
     property public final int id;
     property public final boolean isRoot;
     property public final androidx.ui.core.semantics.SemanticsNode? parent;
+    property public final androidx.ui.geometry.Offset positionInRoot;
     property public final androidx.ui.unit.IntSize size;
   }
 
diff --git a/ui/ui-core/api/restricted_0.1.0-dev16.txt b/ui/ui-core/api/restricted_0.1.0-dev16.txt
index 0780958..8dca1a0 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev16.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev16.txt
@@ -2033,6 +2033,7 @@
     method public int getId();
     method public boolean getMergingEnabled();
     method public androidx.ui.core.semantics.SemanticsNode? getParent();
+    method public androidx.ui.geometry.Offset getPositionInRoot();
     method public androidx.ui.unit.IntSize getSize();
     method public boolean isRoot();
     property public final androidx.ui.unit.PxBounds boundsInRoot;
@@ -2044,6 +2045,7 @@
     property public final int id;
     property public final boolean isRoot;
     property public final androidx.ui.core.semantics.SemanticsNode? parent;
+    property public final androidx.ui.geometry.Offset positionInRoot;
     property public final androidx.ui.unit.IntSize size;
   }
 
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 0780958..8dca1a0 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -2033,6 +2033,7 @@
     method public int getId();
     method public boolean getMergingEnabled();
     method public androidx.ui.core.semantics.SemanticsNode? getParent();
+    method public androidx.ui.geometry.Offset getPositionInRoot();
     method public androidx.ui.unit.IntSize getSize();
     method public boolean isRoot();
     property public final androidx.ui.unit.PxBounds boundsInRoot;
@@ -2044,6 +2045,7 @@
     property public final int id;
     property public final boolean isRoot;
     property public final androidx.ui.core.semantics.SemanticsNode? parent;
+    property public final androidx.ui.geometry.Offset positionInRoot;
     property public final androidx.ui.unit.IntSize size;
   }
 
diff --git a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsNode.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsNode.kt
index 1f1817a..f39b11b 100644
--- a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsNode.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsNode.kt
@@ -24,9 +24,10 @@
 import androidx.ui.core.findClosestParentNode
 import androidx.ui.core.globalBounds
 import androidx.ui.core.globalPosition
+import androidx.ui.core.positionInRoot
+import androidx.ui.geometry.Offset
 import androidx.ui.unit.IntSize
 import androidx.ui.unit.PxBounds
-import androidx.ui.geometry.Offset
 import androidx.ui.util.fastForEach
 
 /**
@@ -71,23 +72,45 @@
 
     // GEOMETRY
 
-    /** The size of the bounding box for this node */
+    /**
+     * The size of the bounding box for this node, with no clipping applied
+     */
     val size: IntSize
         get() {
             return componentNode.coordinates.size
         }
 
-    /** The bounding box for this node relative to the root of this Compose hierarchy */
+    /**
+     * The bounding box for this node relative to the root of this Compose hierarchy, with
+     * clipping applied. To get the bounds with no clipping applied, use
+     * PxBounds([positionInRoot], [size].toSize())
+     */
     val boundsInRoot: PxBounds
         get() {
             return componentNode.coordinates.boundsInRoot
         }
 
+    /**
+     * The position of this node relative to the root of this Compose hierarchy, with no clipping
+     * applied
+     */
+    val positionInRoot: Offset
+        get() {
+            return componentNode.coordinates.positionInRoot
+        }
+
+    /**
+     * The bounding box for this node relative to the screen, with clipping applied. To get the
+     * bounds with no clipping applied, use PxBounds([globalPosition], [size].toSize())
+     */
     val globalBounds: PxBounds
         get() {
             return componentNode.coordinates.globalBounds
         }
 
+    /**
+     * The position of this node relative to the screen, with no clipping applied
+     */
     val globalPosition: Offset
         get() {
             return componentNode.coordinates.globalPosition
diff --git a/ui/ui-foundation/api/0.1.0-dev16.txt b/ui/ui-foundation/api/0.1.0-dev16.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/0.1.0-dev16.txt
+++ b/ui/ui-foundation/api/0.1.0-dev16.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/current.txt b/ui/ui-foundation/api/current.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/current.txt
+++ b/ui/ui-foundation/api/current.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev16.txt b/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev16.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev16.txt
+++ b/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev16.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/public_plus_experimental_current.txt b/ui/ui-foundation/api/public_plus_experimental_current.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/public_plus_experimental_current.txt
+++ b/ui/ui-foundation/api/public_plus_experimental_current.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/restricted_0.1.0-dev16.txt b/ui/ui-foundation/api/restricted_0.1.0-dev16.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/restricted_0.1.0-dev16.txt
+++ b/ui/ui-foundation/api/restricted_0.1.0-dev16.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/api/restricted_current.txt b/ui/ui-foundation/api/restricted_current.txt
index c418a4c..7022063 100644
--- a/ui/ui-foundation/api/restricted_current.txt
+++ b/ui/ui-foundation/api/restricted_current.txt
@@ -264,16 +264,17 @@
     method public static androidx.ui.core.Modifier scrollable(androidx.ui.core.Modifier, androidx.ui.core.gesture.scrollorientationlocking.Orientation orientation, androidx.compose.foundation.gestures.ScrollableController controller, boolean enabled = true, boolean reverseDirection = false, kotlin.jvm.functions.Function1<? super androidx.ui.core.Direction,java.lang.Boolean> canScroll = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.ui.geometry.Offset,kotlin.Unit>  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> >
   }
 
-  public final class ZoomableKt {
-    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableState ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableState zoomableState, kotlin.jvm.functions.Function0<kotlin.Unit>? >
-    method @androidx.compose.Composable public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+  public final class ZoomableController {
+    ctor public ZoomableController(androidx.animation.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
+    method public void smoothScaleBy(float value, androidx.animation.AnimationSpec<java.lang.Float> spec = androidx.animation.SpringSpec(Spring.StiffnessLow), kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+    method public void stopAnimation();
   }
 
-  public final class ZoomableState {
-    ctor public ZoomableState(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta, androidx.animation.AnimationClockObservable animationClock);
-    method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
-    method public void smoothScaleBy(float value, kotlin.jvm.functions.Function2<? super androidx.animation.AnimationEndReason,? super java.lang.Float,kotlin.Unit>? >
+  public final class ZoomableKt {
+    method @androidx.compose.Composable public static androidx.compose.foundation.gestures.ZoomableController rememberZoomableController(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, androidx.compose.foundation.gestures.ZoomableController controller, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>? >
+    method public static androidx.ui.core.Modifier zoomable(androidx.ui.core.Modifier, boolean enabled = true, kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function0<kotlin.Unit>?  kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
   }
 
 }
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
index b2d0212..14f0e3a 100644
--- a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
@@ -24,7 +24,7 @@
 import androidx.compose.foundation.samples.ScrollableColumnSample
 
 val FoundationDemos = DemoCategory("Foundation", listOf(
-    ComposableDemo("Draggable and Scrollable") { HighLevelGesturesDemo() },
+    ComposableDemo("Draggable, Scrollable, Zoomable") { HighLevelGesturesDemo() },
     ComposableDemo("Scrollable Column") { ScrollableColumnSample() },
     ComposableDemo("Controlled Scrollable Row") { ControlledScrollableRowSample() },
     ComposableDemo("Dialog") { DialogSample() },
diff --git a/ui/ui-foundation/samples/src/main/java/androidx/compose/foundation/samples/ZoomableSample.kt b/ui/ui-foundation/samples/src/main/java/androidx/compose/foundation/samples/ZoomableSample.kt
index 3d9193a..fbc2b8b 100644
--- a/ui/ui-foundation/samples/src/main/java/androidx/compose/foundation/samples/ZoomableSample.kt
+++ b/ui/ui-foundation/samples/src/main/java/androidx/compose/foundation/samples/ZoomableSample.kt
@@ -18,6 +18,15 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.Composable
+import androidx.compose.foundation.Box
+import androidx.compose.foundation.ContentGravity
+import androidx.compose.foundation.Text
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.drawBorder
+import androidx.compose.foundation.gestures.rememberZoomableController
+import androidx.compose.foundation.gestures.zoomable
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.preferredSize
 import androidx.compose.getValue
 import androidx.compose.setValue
 import androidx.compose.state
@@ -25,16 +34,7 @@
 import androidx.ui.core.Modifier
 import androidx.ui.core.clipToBounds
 import androidx.ui.core.drawLayer
-import androidx.compose.foundation.Box
-import androidx.compose.foundation.ContentGravity
-import androidx.compose.foundation.Text
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.drawBorder
-import androidx.compose.foundation.gestures.ZoomableState
-import androidx.compose.foundation.gestures.zoomable
 import androidx.ui.graphics.Color
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.preferredSize
 import androidx.ui.unit.dp
 import androidx.ui.unit.sp
 
@@ -46,14 +46,13 @@
         backgroundColor = Color.LightGray
     ) {
         var scale by state(structuralEqualityPolicy()) { 1f }
-        val zoomableState = ZoomableState { scale *= it }
-
+        val zoomableController = rememberZoomableController { scale *= it }
         Box(
             Modifier
-                .zoomable(zoomableState)
+                .zoomable(zoomableController)
                 .clickable(
                     indication = null,
-                     zoomableState.smoothScaleBy(4f) },
+                     zoomableController.smoothScaleBy(4f) },
                     >
                 )
                 .fillMaxSize()
diff --git a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt
index 0dc4545..8dc3a65 100644
--- a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt
+++ b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt
@@ -17,11 +17,12 @@
 package androidx.compose.foundation
 
 import androidx.compose.Composable
+import androidx.compose.mutableStateOf
 import androidx.test.filters.SmallTest
 import androidx.ui.core.Modifier
 import androidx.ui.core.testTag
-import androidx.compose.foundation.gestures.ZoomableState
 import androidx.compose.foundation.gestures.zoomable
+import androidx.compose.foundation.gestures.ZoomableController
 import androidx.ui.geometry.Offset
 import androidx.compose.foundation.layout.preferredSize
 import androidx.ui.test.AnimationClockTestRule
@@ -35,6 +36,7 @@
 import androidx.ui.test.size
 import androidx.ui.unit.dp
 import androidx.ui.unit.toSize
+import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
 import org.junit.Test
@@ -57,12 +59,12 @@
     @Test
     fun zoomable_zoomIn() {
         var cumulativeScale = 1.0f
-        val state = ZoomableState(
+        val controller = ZoomableController(
              cumulativeScale *= it },
             animationClock = clockRule.clock
         )
 
-        setZoomableContent { Modifier.zoomable(state) }
+        setZoomableContent { Modifier.zoomable(controller) }
 
         onNodeWithTag(TEST_TAG).performGesture {
             val leftStartX = center.x - 10
@@ -88,12 +90,12 @@
     @Test
     fun zoomable_zoomOut() {
         var cumulativeScale = 1.0f
-        val state = ZoomableState(
+        val controller = ZoomableController(
              cumulativeScale *= it },
             animationClock = clockRule.clock
         )
 
-        setZoomableContent { Modifier.zoomable(state) }
+        setZoomableContent { Modifier.zoomable(controller) }
 
         onNodeWithTag(TEST_TAG).performGesture {
             val leftStartX = size.toSize().width * EDGE_FUZZ_FACTOR
@@ -119,10 +121,159 @@
     }
 
     @Test
+    fun zoomable_startStop_notify() {
+        var cumulativeScale = 1.0f
+        var startTriggered = 0f
+        var stopTriggered = 0f
+        val controller = ZoomableController(
+             cumulativeScale *= it },
+            animationClock = clockRule.clock
+        )
+
+        setZoomableContent {
+            Modifier
+                .zoomable(controller = controller,
+                     startTriggered++ },
+                     stopTriggered++ }
+                )
+        }
+
+        runOnIdle {
+            Truth.assertThat(startTriggered).isEqualTo(0)
+            Truth.assertThat(stopTriggered).isEqualTo(0)
+        }
+
+        onNodeWithTag(TEST_TAG).performGesture {
+            val leftStartX = size.toSize().width * EDGE_FUZZ_FACTOR
+            val leftEndX = center.x - 10
+            val rightStartX = size.toSize().width * (1 - EDGE_FUZZ_FACTOR)
+            val rightEndX = center.x + 10
+
+            pinch(
+                Offset(leftStartX, center.y),
+                Offset(leftEndX, center.y),
+                Offset(rightStartX, center.y),
+                Offset(rightEndX, center.y)
+            )
+        }
+
+        clockRule.advanceClock(milliseconds = 1000)
+
+        runOnIdle {
+            Truth.assertThat(startTriggered).isEqualTo(1)
+            Truth.assertThat(stopTriggered).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun zoomable_disabledWontCallLambda() {
+        val enabled = mutableStateOf(true)
+        var cumulativeScale = 1.0f
+        val controller = ZoomableController(
+             cumulativeScale *= it },
+            animationClock = clockRule.clock
+        )
+
+        setZoomableContent {
+            Modifier
+                .zoomable(controller = controller, enabled = enabled.value)
+        }
+
+        onNodeWithTag(TEST_TAG).performGesture {
+            val leftStartX = center.x - 10
+            val leftEndX = size.toSize().width * EDGE_FUZZ_FACTOR
+            val rightStartX = center.x + 10
+            val rightEndX = size.toSize().width * (1 - EDGE_FUZZ_FACTOR)
+
+            pinch(
+                Offset(leftStartX, center.y),
+                Offset(leftEndX, center.y),
+                Offset(rightStartX, center.y),
+                Offset(rightEndX, center.y)
+            )
+        }
+
+        clockRule.advanceClock(milliseconds = 1000)
+
+        val prevScale = runOnIdle {
+            assertWithMessage("Should have scaled at least 4x").that(cumulativeScale).isAtLeast(4f)
+            enabled.value = false
+            cumulativeScale
+        }
+
+        onNodeWithTag(TEST_TAG).performGesture {
+            val leftStartX = size.toSize().width * EDGE_FUZZ_FACTOR
+            val leftEndX = center.x - 10
+            val rightStartX = size.toSize().width * (1 - EDGE_FUZZ_FACTOR)
+            val rightEndX = center.x + 10
+
+            pinch(
+                Offset(leftStartX, center.y),
+                Offset(leftEndX, center.y),
+                Offset(rightStartX, center.y),
+                Offset(rightEndX, center.y)
+            )
+        }
+
+        runOnIdle {
+            assertWithMessage("When enabled = false, scale should stay the same")
+                .that(cumulativeScale)
+                .isEqualTo(prevScale)
+        }
+    }
+
+    @Test
+    fun zoomable_callsStop_whenRemoved() {
+        var cumulativeScale = 1.0f
+        var stopTriggered = 0f
+        val controller = ZoomableController(
+             cumulativeScale *= it },
+            animationClock = clockRule.clock
+        )
+
+        setZoomableContent {
+            if (cumulativeScale < 2f) {
+                Modifier
+                    .zoomable(
+                        controller = controller,
+                         stopTriggered++ }
+                    )
+            } else {
+                Modifier
+            }
+        }
+
+        runOnIdle {
+            Truth.assertThat(stopTriggered).isEqualTo(0)
+        }
+
+        onNodeWithTag(TEST_TAG).performGesture {
+            val leftStartX = center.x - 10
+            val leftEndX = size.toSize().width * EDGE_FUZZ_FACTOR
+            val rightStartX = center.x + 10
+            val rightEndX = size.toSize().width * (1 - EDGE_FUZZ_FACTOR)
+
+            pinch(
+                Offset(leftStartX, center.y),
+                Offset(leftEndX, center.y),
+                Offset(rightStartX, center.y),
+                Offset(rightEndX, center.y)
+            )
+        }
+
+        clockRule.advanceClock(milliseconds = 1000)
+
+        runOnIdle {
+            Truth.assertThat(cumulativeScale).isAtLeast(2f)
+            Truth.assertThat(stopTriggered).isEqualTo(1f)
+        }
+    }
+
+    @Test
     fun zoomable_animateTo() {
         var cumulativeScale = 1.0f
         var callbackCount = 0
-        val state = ZoomableState(
+        val state = ZoomableController(
             >
                 cumulativeScale *= it
                 callbackCount += 1
diff --git a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnItemsTest.kt b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnItemsTest.kt
index 764e111..eebe5b4 100644
--- a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnItemsTest.kt
+++ b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnItemsTest.kt
@@ -29,11 +29,13 @@
 import androidx.compose.foundation.Box
 import androidx.compose.foundation.Text
 import androidx.compose.foundation.layout.InnerPadding
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.foundation.layout.preferredSize
 import androidx.compose.foundation.layout.size
 import androidx.ui.test.SemanticsNodeInteraction
 import androidx.ui.test.assertCountEquals
@@ -55,6 +57,7 @@
 import androidx.ui.unit.dp
 import com.google.common.collect.Range
 import com.google.common.truth.IntegerSubject
+import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
@@ -362,6 +365,65 @@
         assertThat(itemBounds.top.toIntPx()).isWithin1PixelFrom(0)
         assertThat(itemBounds.bottom.toIntPx()).isWithin1PixelFrom(50.dp.toIntPx())
     }
+
+    @Test
+    fun lazyColumnWrapsContent() = with(composeTestRule.density) {
+        val itemInsideLazyColumn = "itemInsideLazyColumn"
+        val itemOutsideLazyColumn = "itemOutsideLazyColumn"
+        var sameSizeItems by mutableStateOf(true)
+
+        composeTestRule.setContent {
+            Row {
+                LazyColumnItems(
+                    items = listOf(1, 2),
+                    modifier = Modifier.testTag(LazyColumnItemsTag)
+                ) {
+                    if (it == 1) {
+                        Spacer(Modifier.preferredSize(50.dp).testTag(itemInsideLazyColumn))
+                    } else {
+                        Spacer(Modifier.preferredSize(if (sameSizeItems) 50.dp else 70.dp))
+                    }
+                }
+                Spacer(Modifier.preferredSize(50.dp).testTag(itemOutsideLazyColumn))
+            }
+        }
+
+        onNodeWithTag(itemInsideLazyColumn)
+            .assertIsDisplayed()
+
+        onNodeWithTag(itemOutsideLazyColumn)
+            .assertIsDisplayed()
+
+        var lazyColumnBounds = onNodeWithTag(LazyColumnItemsTag)
+            .getBoundsInRoot()
+
+        Truth.assertThat(lazyColumnBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+        Truth.assertThat(lazyColumnBounds.right.toIntPx()).isWithin1PixelFrom(50.dp.toIntPx())
+        Truth.assertThat(lazyColumnBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+        // TODO: wrap-content on the main-axis must be implemented
+//        Truth.assertThat(itemInsideBounds.bottom.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
+
+        runOnIdle {
+            sameSizeItems = false
+        }
+
+        waitForIdle()
+
+        onNodeWithTag(itemInsideLazyColumn)
+            .assertIsDisplayed()
+
+        onNodeWithTag(itemOutsideLazyColumn)
+            .assertIsDisplayed()
+
+        lazyColumnBounds = onNodeWithTag(LazyColumnItemsTag)
+            .getBoundsInRoot()
+
+        Truth.assertThat(lazyColumnBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+        Truth.assertThat(lazyColumnBounds.right.toIntPx()).isWithin1PixelFrom(70.dp.toIntPx())
+        Truth.assertThat(lazyColumnBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+        // TODO: wrap-content on the main-axis must be implemented
+//        Truth.assertThat(itemInsideBounds.bottom.toIntPx()).isWithin1PixelFrom(120.dp.toIntPx())
+    }
 }
 
 internal fun IntegerSubject.isWithin1PixelFrom(expected: Int) {
diff --git a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowItemsTest.kt b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowItemsTest.kt
index 7e16ea6..9cfe680 100644
--- a/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowItemsTest.kt
+++ b/ui/ui-foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowItemsTest.kt
@@ -16,6 +16,10 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.layout.Column
+import androidx.compose.getValue
+import androidx.compose.mutableStateOf
+import androidx.compose.setValue
 import androidx.test.filters.MediumTest
 import androidx.ui.core.Modifier
 import androidx.ui.core.testTag
@@ -23,12 +27,15 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.Stack
 import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.preferredSize
 import androidx.compose.foundation.layout.preferredWidth
 import androidx.compose.foundation.layout.size
 import androidx.ui.test.assertIsDisplayed
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.getBoundsInRoot
 import androidx.ui.test.onNodeWithTag
+import androidx.ui.test.runOnIdle
+import androidx.ui.test.waitForIdle
 import androidx.ui.unit.dp
 import com.google.common.truth.Truth
 import org.junit.Rule
@@ -189,4 +196,63 @@
         Truth.assertThat(itemBounds.left.toIntPx()).isWithin1PixelFrom(0)
         Truth.assertThat(itemBounds.right.toIntPx()).isWithin1PixelFrom(50.dp.toIntPx())
     }
+
+    @Test
+    fun lazyRowWrapsContent() = with(composeTestRule.density) {
+        val itemInsideLazyRow = "itemInsideLazyRow"
+        val itemOutsideLazyRow = "itemOutsideLazyRow"
+        var sameSizeItems by mutableStateOf(true)
+
+        composeTestRule.setContent {
+            Column {
+                LazyRowItems(
+                    items = listOf(1, 2),
+                    modifier = Modifier.testTag(LazyRowItemsTag)
+                ) {
+                    if (it == 1) {
+                        Spacer(Modifier.preferredSize(50.dp).testTag(itemInsideLazyRow))
+                    } else {
+                        Spacer(Modifier.preferredSize(if (sameSizeItems) 50.dp else 70.dp))
+                    }
+                }
+                Spacer(Modifier.preferredSize(50.dp).testTag(itemOutsideLazyRow))
+            }
+        }
+
+        onNodeWithTag(itemInsideLazyRow)
+            .assertIsDisplayed()
+
+        onNodeWithTag(itemOutsideLazyRow)
+            .assertIsDisplayed()
+
+        var lazyRowBounds = onNodeWithTag(LazyRowItemsTag)
+            .getBoundsInRoot()
+
+        Truth.assertThat(lazyRowBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+        // TODO: wrap-content on the main-axis must be implemented
+//        Truth.assertThat(lazyRowBounds.right.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
+        Truth.assertThat(lazyRowBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+        Truth.assertThat(lazyRowBounds.bottom.toIntPx()).isWithin1PixelFrom(50.dp.toIntPx())
+
+        runOnIdle {
+            sameSizeItems = false
+        }
+
+        waitForIdle()
+
+        onNodeWithTag(itemInsideLazyRow)
+            .assertIsDisplayed()
+
+        onNodeWithTag(itemOutsideLazyRow)
+            .assertIsDisplayed()
+
+        lazyRowBounds = onNodeWithTag(LazyRowItemsTag)
+            .getBoundsInRoot()
+
+        Truth.assertThat(lazyRowBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+        // TODO: wrap-content on the main-axis must be implemented
+//        Truth.assertThat(lazyRowBounds.right.toIntPx()).isWithin1PixelFrom(120.dp.toIntPx())
+        Truth.assertThat(lazyRowBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+        Truth.assertThat(lazyRowBounds.bottom.toIntPx()).isWithin1PixelFrom(70.dp.toIntPx())
+    }
 }
diff --git a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 7aee278..d739d8b 100644
--- a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -157,7 +157,7 @@
  * @param controller [ScrollableController] object that is responsible for redirecting scroll
  * deltas to [ScrollableController.consumeScrollDelta] callback and provides smooth scrolling
  * capabilities
- * @param enabled whether of not scrolling in enabled
+ * @param enabled whether or not scrolling in enabled
  * @param reverseDirection reverse the direction of the scroll, so top to bottom scroll will
  * behave like bottom to top and left to right will behave like right to left.
  * @param canScroll callback to indicate whether or not scroll is allowed for given [Direction]
diff --git a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Zoomable.kt b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Zoomable.kt
index cdaae83..3f093d2 100644
--- a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Zoomable.kt
+++ b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Zoomable.kt
@@ -19,58 +19,75 @@
 import androidx.animation.AnimatedFloat
 import androidx.animation.AnimationClockObservable
 import androidx.animation.AnimationEndReason
+import androidx.animation.AnimationSpec
 import androidx.animation.Spring
 import androidx.animation.SpringSpec
 import androidx.compose.Composable
+import androidx.compose.onDispose
 import androidx.compose.remember
 import androidx.ui.animation.asDisposableClock
 import androidx.ui.core.AnimationClockAmbient
 import androidx.ui.core.Modifier
+import androidx.ui.core.composed
 import androidx.ui.core.gesture.ScaleObserver
 import androidx.ui.core.gesture.scaleGestureFilter
 
 /**
- * Create [ZoomableState] with default [AnimationClockObservable].
+ * Create and remember [ZoomableController] with default [AnimationClockObservable].
  *
  * @param onZoomDelta callback to be invoked when pinch/smooth zooming occurs. The callback
  * receives the delta as the ratio of the new size compared to the old. Callers should update
  * their state and UI in this callback.
  */
 @Composable
-fun ZoomableState(onZoomDelta: (Float) -> Unit): ZoomableState {
+fun rememberZoomableController(onZoomDelta: (Float) -> Unit): ZoomableController {
     val clocks = AnimationClockAmbient.current.asDisposableClock()
-    return remember(clocks) { ZoomableState(onZoomDelta, clocks) }
+    return remember(clocks) { ZoomableController(clocks, onZoomDelta) }
 }
 
 /**
- * State of the [zoomable] composable modifier. Provides smooth scaling capabilities.
+ * Controller to control [zoomable] modifier with. Provides smooth scaling capabilities.
  *
+ * @param animationClock clock observable to run animation on. Consider querying
+ * [AnimationClockAmbient] to get current composition value
  * @param onZoomDelta callback to be invoked when pinch/smooth zooming occurs. The callback
  * receives the delta as the ratio of the new size compared to the old. Callers should update
  * their state and UI in this callback.
- * @param animationClock clock observable to run animation on. Consider querying
- * [AnimationClockAmbient] to get current composition value
  */
-class ZoomableState(val onZoomDelta: (Float) -> Unit, animationClock: AnimationClockObservable) {
+class ZoomableController(
+    animationClock: AnimationClockObservable,
+    val onZoomDelta: (Float) -> Unit
+) {
 
     /**
      * Smooth scale by a ratio of [value] over the current size.
      *
      * @param value ratio over the current size by which to scale
+     * @param spec [AnimationSpec] to be used for smoothScale animation
      * @pram [onEnd] callback invoked when the smooth scaling has ended
      */
     fun smoothScaleBy(
         value: Float,
+        spec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow),
         onEnd: ((endReason: AnimationEndReason, finishValue: Float) -> Unit)? = null
     ) {
         val to = animatedFloat.value * value
         animatedFloat.animateTo(
             to,
             >
-            anim = SpringSpec(stiffness = Spring.StiffnessLow)
+            anim = spec
         )
     }
 
+    /**
+     * Stop any ongoing animation or smooth scaling for this controller
+     *
+     * Call this to stop receiving scrollable deltas in [onZoomDelta]
+     */
+    fun stopAnimation() {
+        animatedFloat.stop()
+    }
+
     internal fun onScale(scaleFactor: Float) = onZoomDelta(scaleFactor)
 
     private val animatedFloat = DeltaAnimatedScale(1f, animationClock, ::onScale)
@@ -79,27 +96,56 @@
 /**
  * Enable zooming of the modified UI element.
  *
- * [ZoomableState.onZoomDelta] will be invoked with the change in proportion of the UI element's
+ * [ZoomableController.onZoomDelta] will be invoked with the change in proportion of the UI element's
  * size at each change in either ratio of the gesture or smooth scaling. Callers should update
  * their state and UI in this callback.
  *
  * @sample androidx.compose.foundation.samples.ZoomableSample
  *
- * @param zoomableState [ZoomableState] object that holds the internal state of this zoomable,
+ * @param controller [ZoomableController] object that holds the internal state of this zoomable,
  * and provides smooth scaling capabilities.
+ * @param enabled whether zooming by gestures is enabled or not
+ * @param onZoomStarted callback to be invoked when zoom has started.
  * @param onZoomStopped callback to be invoked when zoom has stopped.
  */
-@Composable
-fun Modifier.zoomable(zoomableState: ZoomableState, onZoomStopped: (() -> Unit)? = null): Modifier {
-    return scaleGestureFilter(
-            scaleObserver = object : ScaleObserver {
-                override fun onScale(scaleFactor: Float) = zoomableState.onScale(scaleFactor)
+fun Modifier.zoomable(
+    controller: ZoomableController,
+    enabled: Boolean = true,
+    onZoomStarted: (() -> Unit)? = null,
+    onZoomStopped: (() -> Unit)? = null
+) = composed {
+    onDispose {
+        controller.stopAnimation()
+    }
+    scaleGestureFilter(
+        scaleObserver = object : ScaleObserver {
+            override fun onScale(scaleFactor: Float) {
+                if (enabled) {
+                    controller.stopAnimation()
+                    controller.onScale(scaleFactor)
+                }
+            }
 
-                override fun onStop() {
+            override fun onStop() {
+                if (enabled) {
                     onZoomStopped?.invoke()
                 }
             }
-        )
+
+            override fun onCancel() {
+                if (enabled) {
+                    onZoomStopped?.invoke()
+                }
+            }
+
+            override fun onStart() {
+                if (enabled) {
+                    controller.stopAnimation()
+                    onZoomStarted?.invoke()
+                }
+            }
+        }
+    )
 }
 
 /**
@@ -111,16 +157,26 @@
  *
  * @sample androidx.compose.foundation.samples.ZoomableSample
  *
+ * @param enabled whether zooming by gestures is enabled or not
+ * @param onZoomStarted callback to be invoked when zoom has started.
  * @param onZoomStopped callback to be invoked when zoom has stopped.
  * @param onZoomDelta callback to be invoked when pinch/smooth zooming occurs. The callback
  * receives the delta as the ratio of the new size compared to the old. Callers should update
  * their state and UI in this callback.
  */
-@Composable
 fun Modifier.zoomable(
+    enabled: Boolean = true,
+    onZoomStarted: (() -> Unit)? = null,
     onZoomStopped: (() -> Unit)? = null,
     onZoomDelta: (Float) -> Unit
-) = Modifier.zoomable(ZoomableState(onZoomDelta), onZoomStopped)
+) = composed {
+    Modifier.zoomable(
+        controller = rememberZoomableController(onZoomDelta),
+        enabled = enabled,
+        >
+        >
+    )
+}
 
 private class DeltaAnimatedScale(
     initial: Float,
diff --git a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemsState.kt b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemsState.kt
index fde5436..3b759e3 100644
--- a/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemsState.kt
+++ b/ui/ui-foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemsState.kt
@@ -33,8 +33,11 @@
 import androidx.ui.core.Ref
 import androidx.ui.core.Remeasurement
 import androidx.ui.core.RemeasurementModifier
+import androidx.ui.core.constrainHeight
+import androidx.ui.core.constrainWidth
 import androidx.ui.core.subcomposeInto
 import kotlin.math.abs
+import kotlin.math.max
 import kotlin.math.roundToInt
 
 private inline class ScrollDirection(val isForward: Boolean)
@@ -302,6 +305,7 @@
             }
 
             var mainAxisUsed = (-firstItemScrollOffset).roundToInt()
+            var maxCrossAxis = 0
 
             // The index of the first item that should be displayed, regardless of what is
             // currently displayed.  Will be moved forward as we determine what's offscreen
@@ -318,6 +322,8 @@
                 val childMainAxisSize = placeable.mainAxisSize
                 mainAxisUsed += childMainAxisSize
 
+                maxCrossAxis = max(maxCrossAxis, placeable.crossAxisSize)
+
                 if (mainAxisUsed < 0f) {
                     // this item is offscreen, remove it and the offset it took up
                     itemIndexOffset = index + 1
@@ -357,7 +363,19 @@
                 )
             }
 
-            return measureScope.layout(constraints.maxWidth, constraints.maxHeight) {
+            // Wrap the content of the children on the cross axis
+            val layoutWidth = if (isVertical) {
+                constraints.constrainWidth(maxCrossAxis)
+            } else {
+                constraints.maxWidth
+            }
+            val layoutHeight = if (!isVertical) {
+                constraints.constrainHeight(maxCrossAxis)
+            } else {
+                constraints.maxHeight
+            }
+
+            return measureScope.layout(layoutWidth, layoutHeight) {
                 val currentMainAxis = (-firstItemScrollOffset).roundToInt()
                 var x = if (isVertical) 0 else currentMainAxis
                 var y = if (!isVertical) 0 else currentMainAxis
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/assertions/BoundsAssertionsTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/assertions/BoundsAssertionsTest.kt
index 66ac801..53110bf 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/assertions/BoundsAssertionsTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/assertions/BoundsAssertionsTest.kt
@@ -19,11 +19,14 @@
 import androidx.test.filters.MediumTest
 import androidx.ui.core.Alignment
 import androidx.ui.core.Modifier
+import androidx.ui.core.clipToBounds
 import androidx.ui.core.testTag
 import androidx.compose.foundation.Box
 import androidx.compose.foundation.background
 import androidx.ui.graphics.Color
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.ltr
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
@@ -60,7 +63,7 @@
                 .fillMaxSize()
                 .wrapContentSize(Alignment.TopStart)
             ) {
-                Box(modifier = Modifier.padding(start = 50.dp, top = 100.dp)) {
+                Box(modifier = Modifier.ltr.padding(start = 50.dp, top = 100.dp)) {
                     Box(modifier = Modifier
                         .testTag(tag)
                         .size(80.dp, 100.dp)
@@ -99,7 +102,7 @@
     }
 
     @Test
-    fun assertEquals() {
+    fun assertSizeEquals() {
         composeBox()
 
         onNodeWithTag(tag)
@@ -108,7 +111,7 @@
     }
 
     @Test
-    fun assertAtLeast() {
+    fun assertSizeAtLeast() {
         composeBox()
 
         onNodeWithTag(tag)
@@ -119,7 +122,7 @@
     }
 
     @Test
-    fun assertEquals_fail() {
+    fun assertSizeEquals_fail() {
         composeBox()
 
         expectError<AssertionError> {
@@ -134,7 +137,7 @@
     }
 
     @Test
-    fun assertAtLeast_fail() {
+    fun assertSizeAtLeast_fail() {
         composeBox()
 
         expectError<AssertionError> {
@@ -172,4 +175,43 @@
                 .assertPositionInRootIsEqualTo(expectedLeft = 49.dp, expectedTop = 99.dp)
         }
     }
-}
\ No newline at end of file
+
+    private fun composeClippedBox() {
+        composeTestRule.setContent {
+            Box(modifier = Modifier
+                .fillMaxSize()
+                .clipToBounds()
+                .wrapContentSize(Alignment.TopStart)
+            ) {
+                // Box is shifted 30dp to the left and 10dp to the top,
+                // so it is clipped to a size of 50 x 90
+                Box(modifier = Modifier
+                    .testTag(tag)
+                    .ltr
+                    .offset((-30).dp, (-10).dp)
+                    .size(80.dp, 100.dp)
+                    .background(color = Color.Black)
+                )
+            }
+        }
+    }
+
+    @Test
+    fun assertClippedPosition() {
+        composeClippedBox()
+
+        onNodeWithTag(tag)
+            .assertPositionInRootIsEqualTo(expectedLeft = (-30).dp, expectedTop = (-10).dp)
+            .assertLeftPositionInRootIsEqualTo((-30).dp)
+            .assertTopPositionInRootIsEqualTo((-10).dp)
+    }
+
+    @Test
+    fun assertClippedSize() {
+        composeClippedBox()
+
+        onNodeWithTag(tag)
+            .assertWidthIsEqualTo(80.dp)
+            .assertHeightIsEqualTo(100.dp)
+    }
+}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt b/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt
index a5641aa..5515009 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt
@@ -328,7 +328,7 @@
 }
 
 @OptIn(ExperimentalLayoutNodeApi::class)
-private fun SemanticsNode.nodeBoundsInWindow(): Rect {
+private fun SemanticsNode.clippedNodeBoundsInWindow(): Rect {
     val composeView = (componentNode.owner as AndroidOwner).view
     val rootLocationInWindow = intArrayOf(0, 0).let {
         composeView.getLocationInWindow(it)
@@ -342,7 +342,7 @@
     val composeView = (componentNode.owner as AndroidOwner).view
 
     // Window relative bounds of our node
-    val nodeBoundsInWindow = nodeBoundsInWindow()
+    val nodeBoundsInWindow = clippedNodeBoundsInWindow()
     if (nodeBoundsInWindow.width == 0f || nodeBoundsInWindow.height == 0f) {
         return false
     }
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/BoundsAssertions.kt b/ui/ui-test/src/main/java/androidx/ui/test/BoundsAssertions.kt
index 4555bf6..27fa5a9 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/BoundsAssertions.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/BoundsAssertions.kt
@@ -25,6 +25,7 @@
 import androidx.ui.unit.Dp
 import androidx.ui.unit.PxBounds
 import androidx.ui.unit.height
+import androidx.ui.unit.toSize
 import androidx.ui.unit.width
 import kotlin.math.absoluteValue
 
@@ -205,6 +206,11 @@
     }
 }
 
+private val SemanticsNode.unclippedBoundsInRoot: PxBounds
+    get() {
+        return PxBounds(positionInRoot, size.toSize())
+    }
+
 private fun <R> SemanticsNodeInteraction.withDensity(
     operation: Density.(SemanticsNode) -> R
 ): R {
@@ -221,7 +227,7 @@
     @OptIn(ExperimentalLayoutNodeApi::class)
     val density = (node.componentNode.owner as AndroidOwner).density
 
-    assertion.invoke(density, node.boundsInRoot)
+    assertion.invoke(density, node.unclippedBoundsInRoot)
     return this
 }
 
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Output.kt b/ui/ui-test/src/main/java/androidx/ui/test/Output.kt
index c0b05a1..6778ad5 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Output.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Output.kt
@@ -23,6 +23,7 @@
 import androidx.ui.semantics.SemanticsProperties
 import androidx.ui.text.AnnotatedString
 import androidx.ui.unit.PxBounds
+import androidx.ui.unit.toSize
 
 /**
  * Prints all the semantics nodes information it holds into string.
@@ -154,7 +155,7 @@
         sb.append("$nestingIndent |-")
     }
     sb.append("Node #$id at ")
-    sb.append(pxBoundsToShortString(globalBounds))
+    sb.append(pxBoundsToShortString(unclippedGlobalBounds))
 
     if (config.contains(SemanticsProperties.TestTag)) {
         sb.append(", Tag: '")
@@ -201,6 +202,11 @@
     }
 }
 
+private val SemanticsNode.unclippedGlobalBounds: PxBounds
+    get() {
+        return PxBounds(globalPosition, size.toSize())
+    }
+
 private fun pxBoundsToShortString(bounds: PxBounds): String {
     return "(${bounds.left}, ${bounds.top}, ${bounds.right}, ${bounds.bottom})px"
 }
diff --git a/webkit/webkit/api/1.4.0-alpha01.txt b/webkit/webkit/api/1.4.0-alpha01.txt
new file mode 100644
index 0000000..57f9939
--- /dev/null
+++ b/webkit/webkit/api/1.4.0-alpha01.txt
@@ -0,0 +1,270 @@
+// Signature format: 3.0
+package androidx.webkit {
+
+  public abstract class JavaScriptReplyProxy {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(String);
+  }
+
+  public final class ProxyConfig {
+    method public java.util.List<java.lang.String!> getBypassRules();
+    method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
+    field public static final String MATCH_ALL_SCHEMES = "*";
+    field public static final String MATCH_HTTP = "http";
+    field public static final String MATCH_HTTPS = "https";
+  }
+
+  public static final class ProxyConfig.Builder {
+    ctor public ProxyConfig.Builder();
+    ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
+    method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect();
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
+    method public androidx.webkit.ProxyConfig build();
+    method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
+    method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
+  }
+
+  public static final class ProxyConfig.ProxyRule {
+    method public String getSchemeFilter();
+    method public String getUrl();
+  }
+
+  public abstract class ProxyController {
+    method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
+    method public abstract void setProxyOverride(androidx.webkit.ProxyConfig, java.util.concurrent.Executor, Runnable);
+  }
+
+  public abstract class SafeBrowsingResponseCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void backToSafety(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void proceed(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
+  }
+
+  public abstract class ServiceWorkerClientCompat {
+    ctor public ServiceWorkerClientCompat();
+    method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerControllerCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ServiceWorkerControllerCompat getInstance();
+    method public abstract androidx.webkit.ServiceWorkerWebSettingsCompat getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(androidx.webkit.ServiceWorkerClientCompat?);
+  }
+
+  public abstract class ServiceWorkerWebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowContentAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowFileAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getBlockNetworkLoads();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getCacheMode();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowContentAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowFileAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setBlockNetworkLoads(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setCacheMode(int);
+  }
+
+  public class TracingConfig {
+    method public java.util.List<java.lang.String!> getCustomIncludedCategories();
+    method public int getPredefinedCategories();
+    method public int getTracingMode();
+    field public static final int CATEGORIES_ALL = 1; // 0x1
+    field public static final int CATEGORIES_ANDROID_WEBVIEW = 2; // 0x2
+    field public static final int CATEGORIES_FRAME_VIEWER = 64; // 0x40
+    field public static final int CATEGORIES_INPUT_LATENCY = 8; // 0x8
+    field public static final int CATEGORIES_JAVASCRIPT_AND_RENDERING = 32; // 0x20
+    field public static final int CATEGORIES_NONE = 0; // 0x0
+    field public static final int CATEGORIES_RENDERING = 16; // 0x10
+    field public static final int CATEGORIES_WEB_DEVELOPER = 4; // 0x4
+    field public static final int RECORD_CONTINUOUSLY = 1; // 0x1
+    field public static final int RECORD_UNTIL_FULL = 0; // 0x0
+  }
+
+  public static class TracingConfig.Builder {
+    ctor public TracingConfig.Builder();
+    method public androidx.webkit.TracingConfig.Builder addCategories(int...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.lang.String!...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.util.Collection<java.lang.String!>);
+    method public androidx.webkit.TracingConfig build();
+    method public androidx.webkit.TracingConfig.Builder setTracingMode(int);
+  }
+
+  public abstract class TracingController {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.TracingController getInstance();
+    method public abstract boolean isTracing();
+    method public abstract void start(androidx.webkit.TracingConfig);
+    method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
+  }
+
+  public class WebMessageCompat {
+    ctor public WebMessageCompat(String?);
+    ctor public WebMessageCompat(String?, androidx.webkit.WebMessagePortCompat![]?);
+    method public String? getData();
+    method public androidx.webkit.WebMessagePortCompat![]? getPorts();
+  }
+
+  public abstract class WebMessagePortCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_CLOSE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void close();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(androidx.webkit.WebMessageCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(android.os.Handler?, androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+  }
+
+  public abstract static class WebMessagePortCompat.WebMessageCallbackCompat {
+    ctor public WebMessagePortCompat.WebMessageCallbackCompat();
+    method public void onMessage(androidx.webkit.WebMessagePortCompat, androidx.webkit.WebMessageCompat?);
+  }
+
+  public abstract class WebResourceErrorCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract CharSequence getDescription();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getErrorCode();
+  }
+
+  public class WebResourceRequestCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isRedirect(android.webkit.WebResourceRequest);
+  }
+
+  public class WebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getDisabledActionModeMenuItems(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDark(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDarkStrategy(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDark(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDarkStrategy(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+    field public static final int DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING = 2; // 0x2
+    field public static final int DARK_STRATEGY_USER_AGENT_DARKENING_ONLY = 0; // 0x0
+    field public static final int DARK_STRATEGY_WEB_THEME_DARKENING_ONLY = 1; // 0x1
+    field public static final int FORCE_DARK_AUTO = 1; // 0x1
+    field public static final int FORCE_DARK_OFF = 0; // 0x0
+    field public static final int FORCE_DARK_ON = 2; // 0x2
+  }
+
+  public final class WebViewAssetLoader {
+    method @WorkerThread public android.webkit.WebResourceResponse? shouldInterceptRequest(android.net.Uri);
+    field public static final String DEFAULT_DOMAIN = "appassets.androidplatform.net";
+  }
+
+  public static final class WebViewAssetLoader.AssetsPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.AssetsPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.Builder {
+    ctor public WebViewAssetLoader.Builder();
+    method public androidx.webkit.WebViewAssetLoader.Builder addPathHandler(String, androidx.webkit.WebViewAssetLoader.PathHandler);
+    method public androidx.webkit.WebViewAssetLoader build();
+    method public androidx.webkit.WebViewAssetLoader.Builder setDomain(String);
+    method public androidx.webkit.WebViewAssetLoader.Builder setHttpAllowed(boolean);
+  }
+
+  public static final class WebViewAssetLoader.InternalStoragePathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.InternalStoragePathHandler(android.content.Context, java.io.File);
+    method @WorkerThread public android.webkit.WebResourceResponse handle(String);
+  }
+
+  public static interface WebViewAssetLoader.PathHandler {
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.ResourcesPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.ResourcesPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public class WebViewClientCompat extends android.webkit.WebViewClient {
+    ctor public WebViewClientCompat();
+    method @RequiresApi(23) public final void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, android.webkit.WebResourceError);
+    method @RequiresApi(21) @UiThread public void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, androidx.webkit.WebResourceErrorCompat);
+    method @RequiresApi(27) public final void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, android.webkit.SafeBrowsingResponse);
+    method @UiThread public void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, androidx.webkit.SafeBrowsingResponseCompat);
+  }
+
+  public class WebViewCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
+    method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_CHROME_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebChromeClient? getWebChromeClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebViewClient getWebViewClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcess? getWebViewRenderProcess(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcessClient? getWebViewRenderProcessClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isMultiProcessEnabled();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.VISUAL_STATE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.POST_WEB_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postWebMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void removeWebMessageListener(android.webkit.WebView, String);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_WHITELIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String!>, android.webkit.ValueCallback<java.lang.Boolean!>?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRenderProcessClient);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, androidx.webkit.WebViewRenderProcessClient?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.START_SAFE_BROWSING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean!>?);
+  }
+
+  public static interface WebViewCompat.VisualStateCallback {
+    method @UiThread public void onComplete(long);
+  }
+
+  public static interface WebViewCompat.WebMessageListener {
+    method @UiThread public void onPostMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri, boolean, androidx.webkit.JavaScriptReplyProxy);
+  }
+
+  public class WebViewFeature {
+    method public static boolean isFeatureSupported(String);
+    field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
+    field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
+    field public static final String FORCE_DARK = "FORCE_DARK";
+    field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
+    field public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
+    field public static final String GET_WEB_VIEW_CLIENT = "GET_WEB_VIEW_CLIENT";
+    field public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
+    field public static final String MULTI_PROCESS = "MULTI_PROCESS";
+    field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
+    field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
+    field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+    field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
+    field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
+    field public static final String SAFE_BROWSING_ENABLE = "SAFE_BROWSING_ENABLE";
+    field public static final String SAFE_BROWSING_HIT = "SAFE_BROWSING_HIT";
+    field public static final String SAFE_BROWSING_PRIVACY_POLICY_URL = "SAFE_BROWSING_PRIVACY_POLICY_URL";
+    field public static final String SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY = "SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY";
+    field public static final String SAFE_BROWSING_RESPONSE_PROCEED = "SAFE_BROWSING_RESPONSE_PROCEED";
+    field public static final String SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL = "SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL";
+    field public static final String SAFE_BROWSING_WHITELIST = "SAFE_BROWSING_WHITELIST";
+    field public static final String SERVICE_WORKER_BASIC_USAGE = "SERVICE_WORKER_BASIC_USAGE";
+    field public static final String SERVICE_WORKER_BLOCK_NETWORK_LOADS = "SERVICE_WORKER_BLOCK_NETWORK_LOADS";
+    field public static final String SERVICE_WORKER_CACHE_MODE = "SERVICE_WORKER_CACHE_MODE";
+    field public static final String SERVICE_WORKER_CONTENT_ACCESS = "SERVICE_WORKER_CONTENT_ACCESS";
+    field public static final String SERVICE_WORKER_FILE_ACCESS = "SERVICE_WORKER_FILE_ACCESS";
+    field public static final String SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST";
+    field public static final String SHOULD_OVERRIDE_WITH_REDIRECTS = "SHOULD_OVERRIDE_WITH_REDIRECTS";
+    field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
+    field public static final String TRACING_CONTROLLER_BASIC_USAGE = "TRACING_CONTROLLER_BASIC_USAGE";
+    field public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
+    field public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE = "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
+    field public static final String WEB_MESSAGE_LISTENER = "WEB_MESSAGE_LISTENER";
+    field public static final String WEB_MESSAGE_PORT_CLOSE = "WEB_MESSAGE_PORT_CLOSE";
+    field public static final String WEB_MESSAGE_PORT_POST_MESSAGE = "WEB_MESSAGE_PORT_POST_MESSAGE";
+    field public static final String WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK = "WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK";
+    field public static final String WEB_RESOURCE_ERROR_GET_CODE = "WEB_RESOURCE_ERROR_GET_CODE";
+    field public static final String WEB_RESOURCE_ERROR_GET_DESCRIPTION = "WEB_RESOURCE_ERROR_GET_DESCRIPTION";
+    field public static final String WEB_RESOURCE_REQUEST_IS_REDIRECT = "WEB_RESOURCE_REQUEST_IS_REDIRECT";
+    field public static final String WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE = "WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE";
+    field public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
+  }
+
+  public abstract class WebViewRenderProcess {
+    ctor public WebViewRenderProcess();
+    method public abstract boolean terminate();
+  }
+
+  public abstract class WebViewRenderProcessClient {
+    ctor public WebViewRenderProcessClient();
+    method public abstract void onRenderProcessResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+    method public abstract void onRenderProcessUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+  }
+
+}
+
diff --git a/webkit/webkit/api/public_plus_experimental_1.4.0-alpha01.txt b/webkit/webkit/api/public_plus_experimental_1.4.0-alpha01.txt
new file mode 100644
index 0000000..57f9939
--- /dev/null
+++ b/webkit/webkit/api/public_plus_experimental_1.4.0-alpha01.txt
@@ -0,0 +1,270 @@
+// Signature format: 3.0
+package androidx.webkit {
+
+  public abstract class JavaScriptReplyProxy {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(String);
+  }
+
+  public final class ProxyConfig {
+    method public java.util.List<java.lang.String!> getBypassRules();
+    method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
+    field public static final String MATCH_ALL_SCHEMES = "*";
+    field public static final String MATCH_HTTP = "http";
+    field public static final String MATCH_HTTPS = "https";
+  }
+
+  public static final class ProxyConfig.Builder {
+    ctor public ProxyConfig.Builder();
+    ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
+    method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect();
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
+    method public androidx.webkit.ProxyConfig build();
+    method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
+    method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
+  }
+
+  public static final class ProxyConfig.ProxyRule {
+    method public String getSchemeFilter();
+    method public String getUrl();
+  }
+
+  public abstract class ProxyController {
+    method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
+    method public abstract void setProxyOverride(androidx.webkit.ProxyConfig, java.util.concurrent.Executor, Runnable);
+  }
+
+  public abstract class SafeBrowsingResponseCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void backToSafety(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void proceed(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
+  }
+
+  public abstract class ServiceWorkerClientCompat {
+    ctor public ServiceWorkerClientCompat();
+    method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerControllerCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ServiceWorkerControllerCompat getInstance();
+    method public abstract androidx.webkit.ServiceWorkerWebSettingsCompat getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(androidx.webkit.ServiceWorkerClientCompat?);
+  }
+
+  public abstract class ServiceWorkerWebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowContentAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowFileAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getBlockNetworkLoads();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getCacheMode();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowContentAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowFileAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setBlockNetworkLoads(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setCacheMode(int);
+  }
+
+  public class TracingConfig {
+    method public java.util.List<java.lang.String!> getCustomIncludedCategories();
+    method public int getPredefinedCategories();
+    method public int getTracingMode();
+    field public static final int CATEGORIES_ALL = 1; // 0x1
+    field public static final int CATEGORIES_ANDROID_WEBVIEW = 2; // 0x2
+    field public static final int CATEGORIES_FRAME_VIEWER = 64; // 0x40
+    field public static final int CATEGORIES_INPUT_LATENCY = 8; // 0x8
+    field public static final int CATEGORIES_JAVASCRIPT_AND_RENDERING = 32; // 0x20
+    field public static final int CATEGORIES_NONE = 0; // 0x0
+    field public static final int CATEGORIES_RENDERING = 16; // 0x10
+    field public static final int CATEGORIES_WEB_DEVELOPER = 4; // 0x4
+    field public static final int RECORD_CONTINUOUSLY = 1; // 0x1
+    field public static final int RECORD_UNTIL_FULL = 0; // 0x0
+  }
+
+  public static class TracingConfig.Builder {
+    ctor public TracingConfig.Builder();
+    method public androidx.webkit.TracingConfig.Builder addCategories(int...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.lang.String!...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.util.Collection<java.lang.String!>);
+    method public androidx.webkit.TracingConfig build();
+    method public androidx.webkit.TracingConfig.Builder setTracingMode(int);
+  }
+
+  public abstract class TracingController {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.TracingController getInstance();
+    method public abstract boolean isTracing();
+    method public abstract void start(androidx.webkit.TracingConfig);
+    method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
+  }
+
+  public class WebMessageCompat {
+    ctor public WebMessageCompat(String?);
+    ctor public WebMessageCompat(String?, androidx.webkit.WebMessagePortCompat![]?);
+    method public String? getData();
+    method public androidx.webkit.WebMessagePortCompat![]? getPorts();
+  }
+
+  public abstract class WebMessagePortCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_CLOSE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void close();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(androidx.webkit.WebMessageCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(android.os.Handler?, androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+  }
+
+  public abstract static class WebMessagePortCompat.WebMessageCallbackCompat {
+    ctor public WebMessagePortCompat.WebMessageCallbackCompat();
+    method public void onMessage(androidx.webkit.WebMessagePortCompat, androidx.webkit.WebMessageCompat?);
+  }
+
+  public abstract class WebResourceErrorCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract CharSequence getDescription();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getErrorCode();
+  }
+
+  public class WebResourceRequestCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isRedirect(android.webkit.WebResourceRequest);
+  }
+
+  public class WebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getDisabledActionModeMenuItems(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDark(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDarkStrategy(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDark(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDarkStrategy(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+    field public static final int DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING = 2; // 0x2
+    field public static final int DARK_STRATEGY_USER_AGENT_DARKENING_ONLY = 0; // 0x0
+    field public static final int DARK_STRATEGY_WEB_THEME_DARKENING_ONLY = 1; // 0x1
+    field public static final int FORCE_DARK_AUTO = 1; // 0x1
+    field public static final int FORCE_DARK_OFF = 0; // 0x0
+    field public static final int FORCE_DARK_ON = 2; // 0x2
+  }
+
+  public final class WebViewAssetLoader {
+    method @WorkerThread public android.webkit.WebResourceResponse? shouldInterceptRequest(android.net.Uri);
+    field public static final String DEFAULT_DOMAIN = "appassets.androidplatform.net";
+  }
+
+  public static final class WebViewAssetLoader.AssetsPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.AssetsPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.Builder {
+    ctor public WebViewAssetLoader.Builder();
+    method public androidx.webkit.WebViewAssetLoader.Builder addPathHandler(String, androidx.webkit.WebViewAssetLoader.PathHandler);
+    method public androidx.webkit.WebViewAssetLoader build();
+    method public androidx.webkit.WebViewAssetLoader.Builder setDomain(String);
+    method public androidx.webkit.WebViewAssetLoader.Builder setHttpAllowed(boolean);
+  }
+
+  public static final class WebViewAssetLoader.InternalStoragePathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.InternalStoragePathHandler(android.content.Context, java.io.File);
+    method @WorkerThread public android.webkit.WebResourceResponse handle(String);
+  }
+
+  public static interface WebViewAssetLoader.PathHandler {
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.ResourcesPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.ResourcesPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public class WebViewClientCompat extends android.webkit.WebViewClient {
+    ctor public WebViewClientCompat();
+    method @RequiresApi(23) public final void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, android.webkit.WebResourceError);
+    method @RequiresApi(21) @UiThread public void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, androidx.webkit.WebResourceErrorCompat);
+    method @RequiresApi(27) public final void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, android.webkit.SafeBrowsingResponse);
+    method @UiThread public void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, androidx.webkit.SafeBrowsingResponseCompat);
+  }
+
+  public class WebViewCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
+    method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_CHROME_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebChromeClient? getWebChromeClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebViewClient getWebViewClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcess? getWebViewRenderProcess(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcessClient? getWebViewRenderProcessClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isMultiProcessEnabled();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.VISUAL_STATE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.POST_WEB_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postWebMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void removeWebMessageListener(android.webkit.WebView, String);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_WHITELIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String!>, android.webkit.ValueCallback<java.lang.Boolean!>?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRenderProcessClient);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, androidx.webkit.WebViewRenderProcessClient?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.START_SAFE_BROWSING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean!>?);
+  }
+
+  public static interface WebViewCompat.VisualStateCallback {
+    method @UiThread public void onComplete(long);
+  }
+
+  public static interface WebViewCompat.WebMessageListener {
+    method @UiThread public void onPostMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri, boolean, androidx.webkit.JavaScriptReplyProxy);
+  }
+
+  public class WebViewFeature {
+    method public static boolean isFeatureSupported(String);
+    field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
+    field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
+    field public static final String FORCE_DARK = "FORCE_DARK";
+    field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
+    field public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
+    field public static final String GET_WEB_VIEW_CLIENT = "GET_WEB_VIEW_CLIENT";
+    field public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
+    field public static final String MULTI_PROCESS = "MULTI_PROCESS";
+    field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
+    field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
+    field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+    field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
+    field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
+    field public static final String SAFE_BROWSING_ENABLE = "SAFE_BROWSING_ENABLE";
+    field public static final String SAFE_BROWSING_HIT = "SAFE_BROWSING_HIT";
+    field public static final String SAFE_BROWSING_PRIVACY_POLICY_URL = "SAFE_BROWSING_PRIVACY_POLICY_URL";
+    field public static final String SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY = "SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY";
+    field public static final String SAFE_BROWSING_RESPONSE_PROCEED = "SAFE_BROWSING_RESPONSE_PROCEED";
+    field public static final String SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL = "SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL";
+    field public static final String SAFE_BROWSING_WHITELIST = "SAFE_BROWSING_WHITELIST";
+    field public static final String SERVICE_WORKER_BASIC_USAGE = "SERVICE_WORKER_BASIC_USAGE";
+    field public static final String SERVICE_WORKER_BLOCK_NETWORK_LOADS = "SERVICE_WORKER_BLOCK_NETWORK_LOADS";
+    field public static final String SERVICE_WORKER_CACHE_MODE = "SERVICE_WORKER_CACHE_MODE";
+    field public static final String SERVICE_WORKER_CONTENT_ACCESS = "SERVICE_WORKER_CONTENT_ACCESS";
+    field public static final String SERVICE_WORKER_FILE_ACCESS = "SERVICE_WORKER_FILE_ACCESS";
+    field public static final String SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST";
+    field public static final String SHOULD_OVERRIDE_WITH_REDIRECTS = "SHOULD_OVERRIDE_WITH_REDIRECTS";
+    field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
+    field public static final String TRACING_CONTROLLER_BASIC_USAGE = "TRACING_CONTROLLER_BASIC_USAGE";
+    field public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
+    field public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE = "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
+    field public static final String WEB_MESSAGE_LISTENER = "WEB_MESSAGE_LISTENER";
+    field public static final String WEB_MESSAGE_PORT_CLOSE = "WEB_MESSAGE_PORT_CLOSE";
+    field public static final String WEB_MESSAGE_PORT_POST_MESSAGE = "WEB_MESSAGE_PORT_POST_MESSAGE";
+    field public static final String WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK = "WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK";
+    field public static final String WEB_RESOURCE_ERROR_GET_CODE = "WEB_RESOURCE_ERROR_GET_CODE";
+    field public static final String WEB_RESOURCE_ERROR_GET_DESCRIPTION = "WEB_RESOURCE_ERROR_GET_DESCRIPTION";
+    field public static final String WEB_RESOURCE_REQUEST_IS_REDIRECT = "WEB_RESOURCE_REQUEST_IS_REDIRECT";
+    field public static final String WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE = "WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE";
+    field public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
+  }
+
+  public abstract class WebViewRenderProcess {
+    ctor public WebViewRenderProcess();
+    method public abstract boolean terminate();
+  }
+
+  public abstract class WebViewRenderProcessClient {
+    ctor public WebViewRenderProcessClient();
+    method public abstract void onRenderProcessResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+    method public abstract void onRenderProcessUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+  }
+
+}
+
diff --git a/webkit/webkit/api/res-1.4.0-alpha01.txt b/webkit/webkit/api/res-1.4.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webkit/webkit/api/res-1.4.0-alpha01.txt
diff --git a/webkit/webkit/api/restricted_1.4.0-alpha01.txt b/webkit/webkit/api/restricted_1.4.0-alpha01.txt
new file mode 100644
index 0000000..57f9939
--- /dev/null
+++ b/webkit/webkit/api/restricted_1.4.0-alpha01.txt
@@ -0,0 +1,270 @@
+// Signature format: 3.0
+package androidx.webkit {
+
+  public abstract class JavaScriptReplyProxy {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(String);
+  }
+
+  public final class ProxyConfig {
+    method public java.util.List<java.lang.String!> getBypassRules();
+    method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
+    field public static final String MATCH_ALL_SCHEMES = "*";
+    field public static final String MATCH_HTTP = "http";
+    field public static final String MATCH_HTTPS = "https";
+  }
+
+  public static final class ProxyConfig.Builder {
+    ctor public ProxyConfig.Builder();
+    ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
+    method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect();
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
+    method public androidx.webkit.ProxyConfig build();
+    method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
+    method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
+  }
+
+  public static final class ProxyConfig.ProxyRule {
+    method public String getSchemeFilter();
+    method public String getUrl();
+  }
+
+  public abstract class ProxyController {
+    method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
+    method public abstract void setProxyOverride(androidx.webkit.ProxyConfig, java.util.concurrent.Executor, Runnable);
+  }
+
+  public abstract class SafeBrowsingResponseCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void backToSafety(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void proceed(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
+  }
+
+  public abstract class ServiceWorkerClientCompat {
+    ctor public ServiceWorkerClientCompat();
+    method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerControllerCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ServiceWorkerControllerCompat getInstance();
+    method public abstract androidx.webkit.ServiceWorkerWebSettingsCompat getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(androidx.webkit.ServiceWorkerClientCompat?);
+  }
+
+  public abstract class ServiceWorkerWebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowContentAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowFileAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getBlockNetworkLoads();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getCacheMode();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowContentAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowFileAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setBlockNetworkLoads(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setCacheMode(int);
+  }
+
+  public class TracingConfig {
+    method public java.util.List<java.lang.String!> getCustomIncludedCategories();
+    method public int getPredefinedCategories();
+    method public int getTracingMode();
+    field public static final int CATEGORIES_ALL = 1; // 0x1
+    field public static final int CATEGORIES_ANDROID_WEBVIEW = 2; // 0x2
+    field public static final int CATEGORIES_FRAME_VIEWER = 64; // 0x40
+    field public static final int CATEGORIES_INPUT_LATENCY = 8; // 0x8
+    field public static final int CATEGORIES_JAVASCRIPT_AND_RENDERING = 32; // 0x20
+    field public static final int CATEGORIES_NONE = 0; // 0x0
+    field public static final int CATEGORIES_RENDERING = 16; // 0x10
+    field public static final int CATEGORIES_WEB_DEVELOPER = 4; // 0x4
+    field public static final int RECORD_CONTINUOUSLY = 1; // 0x1
+    field public static final int RECORD_UNTIL_FULL = 0; // 0x0
+  }
+
+  public static class TracingConfig.Builder {
+    ctor public TracingConfig.Builder();
+    method public androidx.webkit.TracingConfig.Builder addCategories(int...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.lang.String!...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.util.Collection<java.lang.String!>);
+    method public androidx.webkit.TracingConfig build();
+    method public androidx.webkit.TracingConfig.Builder setTracingMode(int);
+  }
+
+  public abstract class TracingController {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.TracingController getInstance();
+    method public abstract boolean isTracing();
+    method public abstract void start(androidx.webkit.TracingConfig);
+    method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
+  }
+
+  public class WebMessageCompat {
+    ctor public WebMessageCompat(String?);
+    ctor public WebMessageCompat(String?, androidx.webkit.WebMessagePortCompat![]?);
+    method public String? getData();
+    method public androidx.webkit.WebMessagePortCompat![]? getPorts();
+  }
+
+  public abstract class WebMessagePortCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_CLOSE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void close();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(androidx.webkit.WebMessageCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(android.os.Handler?, androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+  }
+
+  public abstract static class WebMessagePortCompat.WebMessageCallbackCompat {
+    ctor public WebMessagePortCompat.WebMessageCallbackCompat();
+    method public void onMessage(androidx.webkit.WebMessagePortCompat, androidx.webkit.WebMessageCompat?);
+  }
+
+  public abstract class WebResourceErrorCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract CharSequence getDescription();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getErrorCode();
+  }
+
+  public class WebResourceRequestCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isRedirect(android.webkit.WebResourceRequest);
+  }
+
+  public class WebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getDisabledActionModeMenuItems(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDark(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDarkStrategy(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDark(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDarkStrategy(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+    field public static final int DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING = 2; // 0x2
+    field public static final int DARK_STRATEGY_USER_AGENT_DARKENING_ONLY = 0; // 0x0
+    field public static final int DARK_STRATEGY_WEB_THEME_DARKENING_ONLY = 1; // 0x1
+    field public static final int FORCE_DARK_AUTO = 1; // 0x1
+    field public static final int FORCE_DARK_OFF = 0; // 0x0
+    field public static final int FORCE_DARK_ON = 2; // 0x2
+  }
+
+  public final class WebViewAssetLoader {
+    method @WorkerThread public android.webkit.WebResourceResponse? shouldInterceptRequest(android.net.Uri);
+    field public static final String DEFAULT_DOMAIN = "appassets.androidplatform.net";
+  }
+
+  public static final class WebViewAssetLoader.AssetsPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.AssetsPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.Builder {
+    ctor public WebViewAssetLoader.Builder();
+    method public androidx.webkit.WebViewAssetLoader.Builder addPathHandler(String, androidx.webkit.WebViewAssetLoader.PathHandler);
+    method public androidx.webkit.WebViewAssetLoader build();
+    method public androidx.webkit.WebViewAssetLoader.Builder setDomain(String);
+    method public androidx.webkit.WebViewAssetLoader.Builder setHttpAllowed(boolean);
+  }
+
+  public static final class WebViewAssetLoader.InternalStoragePathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.InternalStoragePathHandler(android.content.Context, java.io.File);
+    method @WorkerThread public android.webkit.WebResourceResponse handle(String);
+  }
+
+  public static interface WebViewAssetLoader.PathHandler {
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.ResourcesPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.ResourcesPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public class WebViewClientCompat extends android.webkit.WebViewClient {
+    ctor public WebViewClientCompat();
+    method @RequiresApi(23) public final void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, android.webkit.WebResourceError);
+    method @RequiresApi(21) @UiThread public void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, androidx.webkit.WebResourceErrorCompat);
+    method @RequiresApi(27) public final void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, android.webkit.SafeBrowsingResponse);
+    method @UiThread public void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, androidx.webkit.SafeBrowsingResponseCompat);
+  }
+
+  public class WebViewCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
+    method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_CHROME_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebChromeClient? getWebChromeClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebViewClient getWebViewClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcess? getWebViewRenderProcess(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcessClient? getWebViewRenderProcessClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isMultiProcessEnabled();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.VISUAL_STATE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.POST_WEB_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postWebMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void removeWebMessageListener(android.webkit.WebView, String);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_WHITELIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String!>, android.webkit.ValueCallback<java.lang.Boolean!>?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRenderProcessClient);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, androidx.webkit.WebViewRenderProcessClient?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.START_SAFE_BROWSING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean!>?);
+  }
+
+  public static interface WebViewCompat.VisualStateCallback {
+    method @UiThread public void onComplete(long);
+  }
+
+  public static interface WebViewCompat.WebMessageListener {
+    method @UiThread public void onPostMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri, boolean, androidx.webkit.JavaScriptReplyProxy);
+  }
+
+  public class WebViewFeature {
+    method public static boolean isFeatureSupported(String);
+    field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
+    field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
+    field public static final String FORCE_DARK = "FORCE_DARK";
+    field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
+    field public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
+    field public static final String GET_WEB_VIEW_CLIENT = "GET_WEB_VIEW_CLIENT";
+    field public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
+    field public static final String MULTI_PROCESS = "MULTI_PROCESS";
+    field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
+    field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
+    field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+    field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
+    field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
+    field public static final String SAFE_BROWSING_ENABLE = "SAFE_BROWSING_ENABLE";
+    field public static final String SAFE_BROWSING_HIT = "SAFE_BROWSING_HIT";
+    field public static final String SAFE_BROWSING_PRIVACY_POLICY_URL = "SAFE_BROWSING_PRIVACY_POLICY_URL";
+    field public static final String SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY = "SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY";
+    field public static final String SAFE_BROWSING_RESPONSE_PROCEED = "SAFE_BROWSING_RESPONSE_PROCEED";
+    field public static final String SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL = "SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL";
+    field public static final String SAFE_BROWSING_WHITELIST = "SAFE_BROWSING_WHITELIST";
+    field public static final String SERVICE_WORKER_BASIC_USAGE = "SERVICE_WORKER_BASIC_USAGE";
+    field public static final String SERVICE_WORKER_BLOCK_NETWORK_LOADS = "SERVICE_WORKER_BLOCK_NETWORK_LOADS";
+    field public static final String SERVICE_WORKER_CACHE_MODE = "SERVICE_WORKER_CACHE_MODE";
+    field public static final String SERVICE_WORKER_CONTENT_ACCESS = "SERVICE_WORKER_CONTENT_ACCESS";
+    field public static final String SERVICE_WORKER_FILE_ACCESS = "SERVICE_WORKER_FILE_ACCESS";
+    field public static final String SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST";
+    field public static final String SHOULD_OVERRIDE_WITH_REDIRECTS = "SHOULD_OVERRIDE_WITH_REDIRECTS";
+    field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
+    field public static final String TRACING_CONTROLLER_BASIC_USAGE = "TRACING_CONTROLLER_BASIC_USAGE";
+    field public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
+    field public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE = "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
+    field public static final String WEB_MESSAGE_LISTENER = "WEB_MESSAGE_LISTENER";
+    field public static final String WEB_MESSAGE_PORT_CLOSE = "WEB_MESSAGE_PORT_CLOSE";
+    field public static final String WEB_MESSAGE_PORT_POST_MESSAGE = "WEB_MESSAGE_PORT_POST_MESSAGE";
+    field public static final String WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK = "WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK";
+    field public static final String WEB_RESOURCE_ERROR_GET_CODE = "WEB_RESOURCE_ERROR_GET_CODE";
+    field public static final String WEB_RESOURCE_ERROR_GET_DESCRIPTION = "WEB_RESOURCE_ERROR_GET_DESCRIPTION";
+    field public static final String WEB_RESOURCE_REQUEST_IS_REDIRECT = "WEB_RESOURCE_REQUEST_IS_REDIRECT";
+    field public static final String WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE = "WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE";
+    field public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
+  }
+
+  public abstract class WebViewRenderProcess {
+    ctor public WebViewRenderProcess();
+    method public abstract boolean terminate();
+  }
+
+  public abstract class WebViewRenderProcessClient {
+    ctor public WebViewRenderProcessClient();
+    method public abstract void onRenderProcessResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+    method public abstract void onRenderProcessUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+  }
+
+}
+
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index 1ec5043..846a80f 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -302,6 +302,14 @@
      * so as to emulate a dark theme. WebViews that are not attached to the view hierarchy will not
      * be inverted.
      *
+     * <p class="note"> If your app uses a dark theme, WebView will not be inverted. Similarly, if
+     * your app's theme inherits from a {@code DayNight} theme, WebView will not be inverted.
+     * In either of these cases, you should control the mode manually with
+     * {@link ForceDark#FORCE_DARK_ON} or {@link ForceDark#FORCE_DARK_OFF}.
+     *
+     * <p> See <a href="https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#force_dark">
+     * Force Dark documentation</a> for more information.
+     *
      * @see #setForceDark
      */
     public static final int FORCE_DARK_AUTO = WebSettings.FORCE_DARK_AUTO;
@@ -389,8 +397,10 @@
 
     /**
      * In this mode WebView content will be darkened by a user agent and it will ignore the
-     * web page's dark theme if it exists.
-     * See <a href="https://drafts.csswg.org/css-color-adjust-1/">specification</a>
+     * web page's dark theme if it exists. To avoid mixing two different darkening strategies,
+     * the {@code prefers-color-scheme} media query will evaluate to light.
+     *
+     * <p> See <a href="https://drafts.csswg.org/css-color-adjust-1/">specification</a>
      * for more information.
      *
      * @see #setForceDarkStrategy
@@ -403,6 +413,9 @@
      * If web page does not provide dark theme support WebView content will be rendered with a
      * default theme.
      *
+     * <p> See <a href="https://drafts.csswg.org/css-color-adjust-1/">specification</a>
+     * for more information.
+     *
      * @see #setForceDarkStrategy
      */
     public static final int DARK_STRATEGY_WEB_THEME_DARKENING_ONLY =
@@ -410,7 +423,14 @@
 
     /**
      * In this mode WebView content will be darkened by a user agent unless web page supports dark
-     * theme.
+     * theme. WebView determines whether web pages supports dark theme by the presence of
+     * {@code color-scheme} metadata containing "dark" value. For example,
+     * {@code <meta name="color-scheme" content="dark light">"}.
+     * If the metadata is not presented WebView content will be darkened by a user agent and
+     * {@code prefers-color-scheme} media query will evaluate to light.
+     *
+     * <p> See <a href="https://drafts.csswg.org/css-color-adjust-1/">specification</a>
+     * for more information.
      *
      * @see #setForceDarkStrategy
      */