Merge "Move sharetarget module from alpha02 to beta01" into androidx-master-dev
diff --git a/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java b/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java
index 6dc60c9..44810bf 100644
--- a/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java
+++ b/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java
@@ -103,6 +103,11 @@
 
     CustomTabsSessionToken(@Nullable ICustomTabsCallback callbackBinder,
             @Nullable PendingIntent sessionId) {
+        if (callbackBinder == null && sessionId == null) {
+            throw new IllegalStateException("CustomTabsSessionToken must have either a session id "
+                    + "or a callback (or both).");
+        }
+
         mCallbackBinder = callbackBinder;
         mSessionId = sessionId;
 
@@ -193,10 +198,16 @@
     public boolean equals(Object o) {
         if (!(o instanceof CustomTabsSessionToken)) return false;
         CustomTabsSessionToken other = (CustomTabsSessionToken) o;
-        if (mSessionId != null && other.getId() != null) return mSessionId.equals(other.getId());
 
-        return other.getCallbackBinder() != null
-                && other.getCallbackBinder().equals(mCallbackBinder.asBinder());
+        PendingIntent otherSessionId = other.getId();
+        // If one object has a session id and the other one doesn't, they're not equal.
+        if ((mSessionId == null) != (otherSessionId == null)) return false;
+
+        // If both objects have an id, check that they are equal.
+        if (mSessionId != null) return mSessionId.equals(otherSessionId);
+
+        // Otherwise check for binder equality.
+        return getCallbackBinder().equals(other.getCallbackBinder());
     }
 
     /**
diff --git a/browser/src/test/java/androidx/browser/customtabs/CustomTabsSessionTokenTest.java b/browser/src/test/java/androidx/browser/customtabs/CustomTabsSessionTokenTest.java
new file mode 100644
index 0000000..b64cd51
--- /dev/null
+++ b/browser/src/test/java/androidx/browser/customtabs/CustomTabsSessionTokenTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.browser.customtabs;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.support.customtabs.ICustomTabsCallback;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/**
+ * Tests for {@link CustomTabsSessionToken}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@SmallTest
+public class CustomTabsSessionTokenTest {
+    private Context mContext;
+
+    @Before
+    public void setup() {
+        mContext = ApplicationProvider.getApplicationContext();
+    }
+
+    @Test
+    public void testEquality_withId() {
+        CustomTabsSessionToken token1 = new CustomTabsSessionToken(
+                new CustomTabsSessionToken.MockCallback(),
+                createSessionId(27)
+        );
+
+        CustomTabsSessionToken token2 = new CustomTabsSessionToken(
+                new CustomTabsSessionToken.MockCallback(),
+                createSessionId(27)
+        );
+
+        assertEquals(token1, token2);
+    }
+
+    @Test
+    public void testNonEquality_withId() {
+        // Using the same binder to ensure only the id matters.
+        ICustomTabsCallback.Stub binder = new CustomTabsSessionToken.MockCallback();
+
+        CustomTabsSessionToken token1 = new CustomTabsSessionToken(binder, createSessionId(10));
+        CustomTabsSessionToken token2 = new CustomTabsSessionToken(binder, createSessionId(20));
+
+        assertNotEquals(token1, token2);
+    }
+
+    @Test
+    public void testEquality_withBinder() {
+        ICustomTabsCallback.Stub binder = new CustomTabsSessionToken.MockCallback();
+
+        CustomTabsSessionToken token1 = new CustomTabsSessionToken(binder, null);
+        CustomTabsSessionToken token2 = new CustomTabsSessionToken(binder, null);
+
+        assertEquals(token1, token2);
+    }
+
+    @Test
+    public void testNonEquality_withBinder() {
+        ICustomTabsCallback.Stub binder1 = new CustomTabsSessionToken.MockCallback();
+        ICustomTabsCallback.Stub binder2 = new CustomTabsSessionToken.MockCallback();
+
+        CustomTabsSessionToken token1 = new CustomTabsSessionToken(binder1, null);
+        CustomTabsSessionToken token2 = new CustomTabsSessionToken(binder2, null);
+
+        assertNotEquals(token1, token2);
+    }
+
+    @Test
+    public void testNonEquality_mixedIdAndBinder() {
+        // Using the same binder to ensure only the id matters.
+        ICustomTabsCallback.Stub binder = new CustomTabsSessionToken.MockCallback();
+
+        CustomTabsSessionToken token1 = new CustomTabsSessionToken(binder, createSessionId(10));
+        // Tokens cannot be mixed if only one has an id even if the binder is the same.
+        CustomTabsSessionToken token2 = new CustomTabsSessionToken(binder, null);
+
+        assertNotEquals(token1, token2);
+    }
+
+    // This code does the same as CustomTabsClient#createSessionId but that is not necessary for the
+    // test, we just need to create a PendingIntent that uses sessionId as the requestCode.
+    private PendingIntent createSessionId(int sessionId) {
+        return PendingIntent.getActivity(mContext, sessionId, new Intent(), 0);
+    }
+
+    private void assertEquals(CustomTabsSessionToken token1, CustomTabsSessionToken token2) {
+        Assert.assertEquals(token1, token2);
+        Assert.assertEquals(token2, token1);
+
+        Assert.assertEquals(token1.hashCode(), token2.hashCode());
+    }
+
+    private void assertNotEquals(CustomTabsSessionToken token1, CustomTabsSessionToken token2) {
+        Assert.assertNotEquals(token1, token2);
+        Assert.assertNotEquals(token2, token1);
+
+        // I guess technically this could be flaky, but let's hope not...
+        Assert.assertNotEquals(token1.hashCode(), token2.hashCode());
+    }
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 3b2fa99..612ef2c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -55,7 +55,7 @@
     val DYNAMICANIMATION = Version("1.1.0-alpha03")
     val DYNAMICANIMATION_KTX = Version("1.0.0-alpha03")
     val EMOJI = Version("1.1.0-alpha01")
-    val ENTERPRISE = Version("1.0.0-alpha04")
+    val ENTERPRISE = Version("1.0.0-beta01")
     val EXIFINTERFACE = Version("1.1.0-beta02")
     val FRAGMENT = Version("1.2.0-alpha03")
     val FUTURES = Version("1.0.0-rc01")
diff --git a/core/core/api/1.2.0-alpha04.txt b/core/core/api/1.2.0-alpha04.txt
index ca0e30a..fb12c76 100644
--- a/core/core/api/1.2.0-alpha04.txt
+++ b/core/core/api/1.2.0-alpha04.txt
@@ -932,7 +932,8 @@
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
-    method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+    method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+    method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index ca0e30a..fb12c76 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -932,7 +932,8 @@
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
-    method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+    method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+    method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
diff --git a/core/core/api/restricted_1.2.0-alpha04.txt b/core/core/api/restricted_1.2.0-alpha04.txt
index ea4ca3f..50fe0b6 100644
--- a/core/core/api/restricted_1.2.0-alpha04.txt
+++ b/core/core/api/restricted_1.2.0-alpha04.txt
@@ -1035,7 +1035,8 @@
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
-    method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+    method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+    method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index ea4ca3f..50fe0b6 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1035,7 +1035,8 @@
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
-    method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+    method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+    method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
     method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
diff --git a/core/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java
index adcfc35..6d00a7c 100644
--- a/core/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java
@@ -259,7 +259,7 @@
 
         ShortcutInfoCompat compat = mBuilder
                 .setPersons(persons)
-                .setLongLived()
+                .setLongLived(true)
                 .build();
 
         ShortcutInfo shortcut = compat.toShortcutInfo();
diff --git a/core/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java b/core/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java
index 3631ef4..a8a8963 100644
--- a/core/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java
+++ b/core/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java
@@ -494,13 +494,23 @@
         }
 
         /**
+         * @deprecated Use {@ink #setLongLived(boolean)) instead.
+         */
+        @Deprecated
+        @NonNull
+        public Builder setLongLived() {
+            mInfo.mIsLongLived = true;
+            return this;
+        }
+
+        /**
          * Sets if a shortcut would be valid even if it has been unpublished/invisible by the app
          * (as a dynamic or pinned shortcut). If it is long lived, it can be cached by various
          * system services even after it has been unpublished as a dynamic shortcut.
          */
         @NonNull
-        public Builder setLongLived() {
-            mInfo.mIsLongLived = true;
+        public Builder setLongLived(boolean longLived) {
+            mInfo.mIsLongLived = longLived;
             return this;
         }
 
diff --git a/enterprise/feedback/api/1.0.0-beta01.txt b/enterprise/feedback/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..040814c
--- /dev/null
+++ b/enterprise/feedback/api/1.0.0-beta01.txt
@@ -0,0 +1,59 @@
+// Signature format: 3.0
+package androidx.enterprise.feedback {
+
+  public abstract class KeyedAppState {
+    method public static androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder builder();
+    method public abstract String? getData();
+    method public abstract String getKey();
+    method public abstract String? getMessage();
+    method public abstract int getSeverity();
+    field public static final int MAX_DATA_LENGTH = 1000; // 0x3e8
+    field public static final int MAX_KEY_LENGTH = 100; // 0x64
+    field public static final int MAX_MESSAGE_LENGTH = 1000; // 0x3e8
+    field public static final int SEVERITY_ERROR = 2; // 0x2
+    field public static final int SEVERITY_INFO = 1; // 0x1
+  }
+
+  public abstract static class KeyedAppState.KeyedAppStateBuilder {
+    method public androidx.enterprise.feedback.KeyedAppState build();
+    method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setData(String?);
+    method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setKey(String);
+    method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setMessage(String?);
+    method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setSeverity(int);
+  }
+
+  public abstract class KeyedAppStatesReporter {
+    method public static androidx.enterprise.feedback.KeyedAppStatesReporter create(android.content.Context);
+    method public static androidx.enterprise.feedback.KeyedAppStatesReporter create(android.content.Context, java.util.concurrent.Executor);
+    method public abstract void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+    method public abstract void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+  }
+
+  public abstract class KeyedAppStatesService extends android.app.Service {
+    ctor public KeyedAppStatesService();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract void onReceive(java.util.Collection<androidx.enterprise.feedback.ReceivedKeyedAppState!>, boolean);
+  }
+
+  public abstract class ReceivedKeyedAppState {
+    method public static androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder builder();
+    method public abstract String? getData();
+    method public abstract String getKey();
+    method public abstract String? getMessage();
+    method public abstract String getPackageName();
+    method public abstract int getSeverity();
+    method public abstract long getTimestamp();
+  }
+
+  public abstract static class ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder {
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState build();
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setData(String?);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setKey(String);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setMessage(String?);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setPackageName(String);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setSeverity(int);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setTimestamp(long);
+  }
+
+}
+
diff --git a/enterprise/feedback/api/res-1.0.0-beta01.txt b/enterprise/feedback/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/enterprise/feedback/api/res-1.0.0-beta01.txt
diff --git a/enterprise/feedback/api/restricted_1.0.0-beta01.txt b/enterprise/feedback/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..040814c
--- /dev/null
+++ b/enterprise/feedback/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,59 @@
+// Signature format: 3.0
+package androidx.enterprise.feedback {
+
+  public abstract class KeyedAppState {
+    method public static androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder builder();
+    method public abstract String? getData();
+    method public abstract String getKey();
+    method public abstract String? getMessage();
+    method public abstract int getSeverity();
+    field public static final int MAX_DATA_LENGTH = 1000; // 0x3e8
+    field public static final int MAX_KEY_LENGTH = 100; // 0x64
+    field public static final int MAX_MESSAGE_LENGTH = 1000; // 0x3e8
+    field public static final int SEVERITY_ERROR = 2; // 0x2
+    field public static final int SEVERITY_INFO = 1; // 0x1
+  }
+
+  public abstract static class KeyedAppState.KeyedAppStateBuilder {
+    method public androidx.enterprise.feedback.KeyedAppState build();
+    method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setData(String?);
+    method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setKey(String);
+    method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setMessage(String?);
+    method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setSeverity(int);
+  }
+
+  public abstract class KeyedAppStatesReporter {
+    method public static androidx.enterprise.feedback.KeyedAppStatesReporter create(android.content.Context);
+    method public static androidx.enterprise.feedback.KeyedAppStatesReporter create(android.content.Context, java.util.concurrent.Executor);
+    method public abstract void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+    method public abstract void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+  }
+
+  public abstract class KeyedAppStatesService extends android.app.Service {
+    ctor public KeyedAppStatesService();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract void onReceive(java.util.Collection<androidx.enterprise.feedback.ReceivedKeyedAppState!>, boolean);
+  }
+
+  public abstract class ReceivedKeyedAppState {
+    method public static androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder builder();
+    method public abstract String? getData();
+    method public abstract String getKey();
+    method public abstract String? getMessage();
+    method public abstract String getPackageName();
+    method public abstract int getSeverity();
+    method public abstract long getTimestamp();
+  }
+
+  public abstract static class ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder {
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState build();
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setData(String?);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setKey(String);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setMessage(String?);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setPackageName(String);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setSeverity(int);
+    method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setTimestamp(long);
+  }
+
+}
+
diff --git a/enterprise/feedback/testing/api/1.0.0-beta01.txt b/enterprise/feedback/testing/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..9c90bc0
--- /dev/null
+++ b/enterprise/feedback/testing/api/1.0.0-beta01.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.enterprise.feedback {
+
+  public class FakeKeyedAppStatesReporter extends androidx.enterprise.feedback.KeyedAppStatesReporter {
+    method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getKeyedAppStates();
+    method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getKeyedAppStatesByKey();
+    method public int getNumberOfUploads();
+    method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getOnDeviceKeyedAppStates();
+    method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getOnDeviceKeyedAppStatesByKey();
+    method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getUploadedKeyedAppStates();
+    method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getUploadedKeyedAppStatesByKey();
+    method public void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+    method public void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+  }
+
+}
+
diff --git a/enterprise/feedback/testing/api/res-1.0.0-beta01.txt b/enterprise/feedback/testing/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/enterprise/feedback/testing/api/res-1.0.0-beta01.txt
diff --git a/enterprise/feedback/testing/api/restricted_1.0.0-beta01.txt b/enterprise/feedback/testing/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..9c90bc0
--- /dev/null
+++ b/enterprise/feedback/testing/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.enterprise.feedback {
+
+  public class FakeKeyedAppStatesReporter extends androidx.enterprise.feedback.KeyedAppStatesReporter {
+    method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getKeyedAppStates();
+    method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getKeyedAppStatesByKey();
+    method public int getNumberOfUploads();
+    method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getOnDeviceKeyedAppStates();
+    method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getOnDeviceKeyedAppStatesByKey();
+    method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getUploadedKeyedAppStates();
+    method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getUploadedKeyedAppStatesByKey();
+    method public void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+    method public void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+  }
+
+}
+
diff --git a/lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha04.txt b/lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha04.txt
index 26f3e49..c8887ad 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha04.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha04.txt
@@ -1,25 +1,19 @@
 // Signature format: 3.0
 package androidx.lifecycle {
 
-  public final class CoroutineLiveDataApi26Kt {
-    ctor public CoroutineLiveDataApi26Kt();
-    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
-  }
-
   public final class CoroutineLiveDataKt {
     ctor public CoroutineLiveDataKt();
     method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
   }
 
-  public final class FlowLiveDataApi26Kt {
-    ctor public FlowLiveDataApi26Kt();
-    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
-  }
-
-  public final class FlowLiveDataKt {
-    ctor public FlowLiveDataKt();
+  public final class FlowLiveDataConversions {
+    ctor public FlowLiveDataConversions();
     method public static <T> kotlinx.coroutines.flow.Flow<T> asFlow(androidx.lifecycle.LiveData<T>);
     method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L);
+    method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext);
+    method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
   }
 
   public interface LiveDataScope<T> {
diff --git a/lifecycle/lifecycle-livedata-ktx/api/current.txt b/lifecycle/lifecycle-livedata-ktx/api/current.txt
index 26f3e49..c8887ad 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/current.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/current.txt
@@ -1,25 +1,19 @@
 // Signature format: 3.0
 package androidx.lifecycle {
 
-  public final class CoroutineLiveDataApi26Kt {
-    ctor public CoroutineLiveDataApi26Kt();
-    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
-  }
-
   public final class CoroutineLiveDataKt {
     ctor public CoroutineLiveDataKt();
     method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
   }
 
-  public final class FlowLiveDataApi26Kt {
-    ctor public FlowLiveDataApi26Kt();
-    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
-  }
-
-  public final class FlowLiveDataKt {
-    ctor public FlowLiveDataKt();
+  public final class FlowLiveDataConversions {
+    ctor public FlowLiveDataConversions();
     method public static <T> kotlinx.coroutines.flow.Flow<T> asFlow(androidx.lifecycle.LiveData<T>);
     method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L);
+    method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext);
+    method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
   }
 
   public interface LiveDataScope<T> {
diff --git a/lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha04.txt b/lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha04.txt
index 26f3e49..c8887ad 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha04.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha04.txt
@@ -1,25 +1,19 @@
 // Signature format: 3.0
 package androidx.lifecycle {
 
-  public final class CoroutineLiveDataApi26Kt {
-    ctor public CoroutineLiveDataApi26Kt();
-    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
-  }
-
   public final class CoroutineLiveDataKt {
     ctor public CoroutineLiveDataKt();
     method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
   }
 
-  public final class FlowLiveDataApi26Kt {
-    ctor public FlowLiveDataApi26Kt();
-    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
-  }
-
-  public final class FlowLiveDataKt {
-    ctor public FlowLiveDataKt();
+  public final class FlowLiveDataConversions {
+    ctor public FlowLiveDataConversions();
     method public static <T> kotlinx.coroutines.flow.Flow<T> asFlow(androidx.lifecycle.LiveData<T>);
     method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L);
+    method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext);
+    method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
   }
 
   public interface LiveDataScope<T> {
diff --git a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
index 26f3e49..c8887ad 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
@@ -1,25 +1,19 @@
 // Signature format: 3.0
 package androidx.lifecycle {
 
-  public final class CoroutineLiveDataApi26Kt {
-    ctor public CoroutineLiveDataApi26Kt();
-    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
-  }
-
   public final class CoroutineLiveDataKt {
     ctor public CoroutineLiveDataKt();
     method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
   }
 
-  public final class FlowLiveDataApi26Kt {
-    ctor public FlowLiveDataApi26Kt();
-    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
-  }
-
-  public final class FlowLiveDataKt {
-    ctor public FlowLiveDataKt();
+  public final class FlowLiveDataConversions {
+    ctor public FlowLiveDataConversions();
     method public static <T> kotlinx.coroutines.flow.Flow<T> asFlow(androidx.lifecycle.LiveData<T>);
     method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L);
+    method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext);
+    method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
   }
 
   public interface LiveDataScope<T> {
diff --git a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
index f6e7ea8..c0841ac 100644
--- a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
+++ b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
@@ -16,7 +16,9 @@
 
 package androidx.lifecycle
 
+import android.os.Build
 import androidx.annotation.MainThread
+import androidx.annotation.RequiresApi
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.DisposableHandle
@@ -25,6 +27,7 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import java.time.Duration
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
@@ -324,4 +327,109 @@
     context: CoroutineContext = EmptyCoroutineContext,
     timeoutInMs: Long = DEFAULT_TIMEOUT,
     @BuilderInference block: suspend LiveDataScope<T>.() -> Unit
-): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
\ No newline at end of file
+): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
+
+/**
+ * Builds a LiveData that has values yielded from the given [block] that executes on a
+ * [LiveDataScope].
+ *
+ * The [block] starts executing when the returned [LiveData] becomes active ([LiveData.onActive]).
+ * If the [LiveData] becomes inactive ([LiveData.onInactive]) while the [block] is executing, it
+ * will be cancelled after the [timeout] duration unless the [LiveData] becomes active again
+ * before that timeout (to gracefully handle cases like Activity rotation). Any value
+ * [LiveDataScope.emit]ed from a cancelled [block] will be ignored.
+ *
+ * After a cancellation, if the [LiveData] becomes active again, the [block] will be re-executed
+ * from the beginning. If you would like to continue the operations based on where it was stopped
+ * last, you can use the [LiveDataScope.latestValue] function to get the last
+ * [LiveDataScope.emit]ed value.
+
+ * If the [block] completes successfully *or* is cancelled due to reasons other than [LiveData]
+ * becoming inactive, it *will not* be re-executed even after [LiveData] goes through active
+ * inactive cycle.
+ *
+ * As a best practice, it is important for the [block] to cooperate in cancellation. See kotlin
+ * coroutines documentation for details
+ * https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html.
+ *
+ * ```
+ * // a simple LiveData that receives value 3, 3 seconds after being observed for the first time.
+ * val data : LiveData<Int> = liveData {
+ *     delay(3000)
+ *     emit(3)
+ * }
+ *
+ *
+ * // a LiveData that fetches a `User` object based on a `userId` and refreshes it every 30 seconds
+ * // as long as it is observed
+ * val userId : LiveData<String> = ...
+ * val user = userId.switchMap { id ->
+ *     liveData {
+ *       while(true) {
+ *         // note that `while(true)` is fine because the `delay(30_000)` below will cooperate in
+ *         // cancellation if LiveData is not actively observed anymore
+ *         val data = api.fetch(id) // errors are ignored for brevity
+ *         emit(data)
+ *         delay(30_000)
+ *       }
+ *     }
+ * }
+ *
+ * // A retrying data fetcher with doubling back-off
+ * val user = liveData {
+ *     var backOffTime = 1_000
+ *     var succeeded = false
+ *     while(!succeeded) {
+ *         try {
+ *             emit(api.fetch(id))
+ *             succeeded = true
+ *         } catch(ioError : IOException) {
+ *             delay(backOffTime)
+ *             backOffTime *= minOf(backOffTime * 2, 60_000)
+ *         }
+ *     }
+ * }
+ *
+ * // a LiveData that tries to load the `User` from local cache first and then tries to fetch
+ * // from the server and also yields the updated value
+ * val user = liveData {
+ *     // dispatch loading first
+ *     emit(LOADING(id))
+ *     // check local storage
+ *     val cached = cache.loadUser(id)
+ *     if (cached != null) {
+ *         emit(cached)
+ *     }
+ *     if (cached == null || cached.isStale()) {
+ *         val fresh = api.fetch(id) // errors are ignored for brevity
+ *         cache.save(fresh)
+ *         emit(fresh)
+ *     }
+ * }
+ *
+ * // a LiveData that immediately receives a LiveData<User> from the database and yields it as a
+ * // source but also tries to back-fill the database from the server
+ * val user = liveData {
+ *     val fromDb: LiveData<User> = roomDatabase.loadUser(id)
+ *     emitSource(fromDb)
+ *     val updated = api.fetch(id) // errors are ignored for brevity
+ *     // Since we are using Room here, updating the database will update the `fromDb` LiveData
+ *     // that was obtained above. See Room's documentation for more details.
+ *     // https://developer.android.com/training/data-storage/room/accessing-data#query-observable
+ *     roomDatabase.insert(updated)
+ * }
+ * ```
+ *
+ * * @param context The CoroutineContext to run the given block in. Defaults to
+ * [EmptyCoroutineContext] combined with [Dispatchers.Main].
+ * @param timeout The timeout duration before cancelling the block if there are no active observers
+ * ([LiveData.hasActiveObservers].
+ * @param block The block to run when the [LiveData] has active observers.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+@UseExperimental(ExperimentalTypeInference::class)
+fun <T> liveData(
+    context: CoroutineContext = EmptyCoroutineContext,
+    timeout: Duration,
+    @BuilderInference block: suspend LiveDataScope<T>.() -> Unit
+): LiveData<T> = CoroutineLiveData(context, timeout.toMillis(), block)
diff --git a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveDataApi26.kt b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveDataApi26.kt
deleted file mode 100644
index 57b7756..0000000
--- a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveDataApi26.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.lifecycle
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import kotlinx.coroutines.Dispatchers
-import java.time.Duration
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-import kotlin.experimental.ExperimentalTypeInference
-
-// this function resides in a separate file to avoid parsing API 26 parameter (Duration) in older
-// versions of the platform
-/**
- * Builds a LiveData that has values yielded from the given [block] that executes on a
- * [LiveDataScope].
- *
- * The [block] starts executing when the returned [LiveData] becomes active ([LiveData.onActive]).
- * If the [LiveData] becomes inactive ([LiveData.onInactive]) while the [block] is executing, it
- * will be cancelled after the [timeout] duration unless the [LiveData] becomes active again
- * before that timeout (to gracefully handle cases like Activity rotation). Any value
- * [LiveDataScope.emit]ed from a cancelled [block] will be ignored.
- *
- * After a cancellation, if the [LiveData] becomes active again, the [block] will be re-executed
- * from the beginning. If you would like to continue the operations based on where it was stopped
- * last, you can use the [LiveDataScope.latestValue] function to get the last
- * [LiveDataScope.emit]ed value.
-
- * If the [block] completes successfully *or* is cancelled due to reasons other than [LiveData]
- * becoming inactive, it *will not* be re-executed even after [LiveData] goes through active
- * inactive cycle.
- *
- * As a best practice, it is important for the [block] to cooperate in cancellation. See kotlin
- * coroutines documentation for details
- * https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html.
- *
- * ```
- * // a simple LiveData that receives value 3, 3 seconds after being observed for the first time.
- * val data : LiveData<Int> = liveData {
- *     delay(3000)
- *     emit(3)
- * }
- *
- *
- * // a LiveData that fetches a `User` object based on a `userId` and refreshes it every 30 seconds
- * // as long as it is observed
- * val userId : LiveData<String> = ...
- * val user = userId.switchMap { id ->
- *     liveData {
- *       while(true) {
- *         // note that `while(true)` is fine because the `delay(30_000)` below will cooperate in
- *         // cancellation if LiveData is not actively observed anymore
- *         val data = api.fetch(id) // errors are ignored for brevity
- *         emit(data)
- *         delay(30_000)
- *       }
- *     }
- * }
- *
- * // A retrying data fetcher with doubling back-off
- * val user = liveData {
- *     var backOffTime = 1_000
- *     var succeeded = false
- *     while(!succeeded) {
- *         try {
- *             emit(api.fetch(id))
- *             succeeded = true
- *         } catch(ioError : IOException) {
- *             delay(backOffTime)
- *             backOffTime *= minOf(backOffTime * 2, 60_000)
- *         }
- *     }
- * }
- *
- * // a LiveData that tries to load the `User` from local cache first and then tries to fetch
- * // from the server and also yields the updated value
- * val user = liveData {
- *     // dispatch loading first
- *     emit(LOADING(id))
- *     // check local storage
- *     val cached = cache.loadUser(id)
- *     if (cached != null) {
- *         emit(cached)
- *     }
- *     if (cached == null || cached.isStale()) {
- *         val fresh = api.fetch(id) // errors are ignored for brevity
- *         cache.save(fresh)
- *         emit(fresh)
- *     }
- * }
- *
- * // a LiveData that immediately receives a LiveData<User> from the database and yields it as a
- * // source but also tries to back-fill the database from the server
- * val user = liveData {
- *     val fromDb: LiveData<User> = roomDatabase.loadUser(id)
- *     emitSource(fromDb)
- *     val updated = api.fetch(id) // errors are ignored for brevity
- *     // Since we are using Room here, updating the database will update the `fromDb` LiveData
- *     // that was obtained above. See Room's documentation for more details.
- *     // https://developer.android.com/training/data-storage/room/accessing-data#query-observable
- *     roomDatabase.insert(updated)
- * }
- * ```
- *
- * * @param context The CoroutineContext to run the given block in. Defaults to
- * [EmptyCoroutineContext] combined with [Dispatchers.Main].
- * @param timeout The timeout duration before cancelling the block if there are no active observers
- * ([LiveData.hasActiveObservers].
- * @param block The block to run when the [LiveData] has active observers.
- */
-@RequiresApi(Build.VERSION_CODES.O)
-@UseExperimental(ExperimentalTypeInference::class)
-fun <T> liveData(
-    context: CoroutineContext = EmptyCoroutineContext,
-    timeout: Duration,
-    @BuilderInference block: suspend LiveDataScope<T>.() -> Unit
-): LiveData<T> = CoroutineLiveData(context, timeout.toMillis(), block)
diff --git a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveData.kt b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveData.kt
index 3b2c2f25..f3d3207 100644
--- a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveData.kt
+++ b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveData.kt
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
+@file:JvmName("FlowLiveDataConversions")
+
 package androidx.lifecycle
 
+import android.os.Build
+import androidx.annotation.RequiresApi
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
@@ -25,6 +29,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.flow
+import java.time.Duration
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 
@@ -56,6 +61,7 @@
  * @param timeoutInMs The timeout in ms before cancelling the block if there are no active observers
  * ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
  */
+@JvmOverloads
 fun <T> Flow<T>.asLiveData(
     context: CoroutineContext = EmptyCoroutineContext,
     timeoutInMs: Long = DEFAULT_TIMEOUT
@@ -93,4 +99,38 @@
             removeObserver(observer)
         }
     }
-}
\ No newline at end of file
+}
+
+/**
+ * Creates a LiveData that has values collected from the origin [Flow].
+ *
+ * The upstream flow collection starts when the returned [LiveData] becomes active
+ * ([LiveData.onActive]).
+ * If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow has not completed,
+ * the flow collection will be cancelled after [timeout] unless the [LiveData]
+ * becomes active again before that timeout (to gracefully handle cases like Activity rotation).
+ *
+ * After a cancellation, if the [LiveData] becomes active again, the upstream flow collection will
+ * be re-executed.
+ *
+ * If the upstream flow completes successfully *or* is cancelled due to reasons other than
+ * [LiveData] becoming inactive, it *will not* be re-collected even after [LiveData] goes through
+ * active inactive cycle.
+ *
+ * If flow completes with an exception, then exception will be delivered to the
+ * [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context].
+ * By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's
+ * thread [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is
+ * expected to throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow
+ * to emit a helpful error object.
+ *
+ * @param context The CoroutineContext to collect the upstream flow in. Defaults to
+ * [EmptyCoroutineContext] combined with [Dispatchers.Main]
+ * @param timeout The timeout in ms before cancelling the block if there are no active observers
+ * ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+fun <T> Flow<T>.asLiveData(
+    context: CoroutineContext = EmptyCoroutineContext,
+    timeout: Duration
+): LiveData<T> = asLiveData(context, timeout.toMillis())
\ No newline at end of file
diff --git a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveDataApi26.kt b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveDataApi26.kt
deleted file mode 100644
index ac2643d..0000000
--- a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveDataApi26.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.lifecycle
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.Flow
-import java.time.Duration
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-
-/**
- * Creates a LiveData that has values collected from the origin [Flow].
- *
- * The upstream flow collection starts when the returned [LiveData] becomes active
- * ([LiveData.onActive]).
- * If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow has not completed,
- * the flow collection will be cancelled after [timeout] unless the [LiveData]
- * becomes active again before that timeout (to gracefully handle cases like Activity rotation).
- *
- * After a cancellation, if the [LiveData] becomes active again, the upstream flow collection will
- * be re-executed.
- *
- * If the upstream flow completes successfully *or* is cancelled due to reasons other than
- * [LiveData] becoming inactive, it *will not* be re-collected even after [LiveData] goes through
- * active inactive cycle.
- *
- * If flow completes with an exception, then exception will be delivered to the
- * [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context].
- * By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's
- * thread [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is
- * expected to throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow
- * to emit a helpful error object.
- *
- * @param context The CoroutineContext to collect the upstream flow in. Defaults to
- * [EmptyCoroutineContext] combined with [Dispatchers.Main]
- * @param timeout The timeout in ms before cancelling the block if there are no active observers
- * ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
- */
-@RequiresApi(Build.VERSION_CODES.O)
-fun <T> Flow<T>.asLiveData(
-    context: CoroutineContext = EmptyCoroutineContext,
-    timeout: Duration
-): LiveData<T> = asLiveData(context, timeout.toMillis())
\ No newline at end of file
diff --git a/lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/LiveDataFlowJavaTest.java b/lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/LiveDataFlowJavaTest.java
new file mode 100644
index 0000000..c44f948
--- /dev/null
+++ b/lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/LiveDataFlowJavaTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import kotlinx.coroutines.flow.Flow;
+
+@RunWith(JUnit4.class)
+public class LiveDataFlowJavaTest {
+
+    /**
+     * A purpose of this function only to show case java interop.
+     * Real tests are in {@link FlowAsLiveDataTest} and {@link LiveDataAsFlowTest}
+     */
+    @Test
+    public void noOp() {
+        LiveData<String> liveData = new MutableLiveData<>("no-op");
+        Flow<String> flow = FlowLiveDataConversions.asFlow(liveData);
+        FlowLiveDataConversions.asLiveData(flow);
+    }
+}
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
index e542705..3df4b6d 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
@@ -44,7 +44,6 @@
 import androidx.media2.common.SessionPlayer;
 import androidx.media2.common.SessionPlayer.TrackInfo;
 import androidx.media2.session.MediaController;
-import androidx.media2.widget.test.R;
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
 
@@ -90,10 +89,11 @@
     @Before
     public void setup() throws Throwable {
         mActivity = mActivityRule.getActivity();
-        mMediaControlView = mActivity.findViewById(R.id.mediacontrolview);
+        mMediaControlView = mActivity.findViewById(
+                androidx.media2.widget.test.R.id.mediacontrolview);
 
         Uri fileSchemeUri = Uri.parse("android.resource://" + mContext.getPackageName() + "/"
-                + R.raw.test_file_scheme_video);
+                + androidx.media2.widget.test.R.raw.test_file_scheme_video);
         mFileSchemeMediaItem = createTestMediaItem(fileSchemeUri);
 
         setKeepScreenOn(mActivityRule);
@@ -355,7 +355,7 @@
     @Test
     public void testButtonVisibilityForMusicFile() throws Throwable {
         Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/"
-                + R.raw.test_music);
+                + androidx.media2.widget.test.R.raw.test_music);
         final MediaItem uriMediaItem = createTestMediaItem(uri);
 
         final CountDownLatch latch = new CountDownLatch(1);
@@ -379,7 +379,7 @@
     @Test
     public void testUpdateAndSelectSubtitleTrack() throws Throwable {
         Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/"
-                + R.raw.testvideo_with_2_subtitle_tracks);
+                + androidx.media2.widget.test.R.raw.testvideo_with_2_subtitle_tracks);
 
         final String subtitleTrackOffText = mContext.getResources().getString(
                 R.string.MediaControlView_subtitle_off_text);
@@ -471,7 +471,8 @@
         mActivityRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                ViewGroup layout = mActivity.findViewById(R.id.framelayout);
+                ViewGroup layout = mActivity.findViewById(
+                        androidx.media2.widget.test.R.id.framelayout);
                 layout.removeView(mMediaControlView);
                 mMediaControlView = new MediaControlView(mActivity);
                 if (playerWrapper.mPlayer != null) {
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java
index fed585d..9f2642a 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java
@@ -29,7 +29,6 @@
 import android.view.View;
 
 import androidx.annotation.NonNull;
-import androidx.media2.widget.test.R;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
@@ -60,7 +59,8 @@
     @Before
     public void setup() throws Throwable {
         mActivity = mActivityRule.getActivity();
-        mMediaControlView = mActivity.findViewById(R.id.mediacontrolview);
+        mMediaControlView = mActivity.findViewById(
+                androidx.media2.widget.test.R.id.mediacontrolview);
 
         setKeepScreenOn(mActivityRule);
         checkAttachedToWindow(mMediaControlView);
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
index e005a01..19b36f11 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
@@ -141,6 +141,8 @@
         return list;
     }
 
+    // TODO(b/138091975) Do not ignore returned Futures if feasible.
+    @SuppressWarnings("FutureReturnValueIgnored")
     PlayerWrapper createPlayerWrapperOfController(@NonNull PlayerWrapper.PlayerCallback callback,
             @Nullable MediaItem item, @Nullable List<MediaItem> playlist) {
         prepareLooper();
@@ -169,6 +171,8 @@
         return wrapper;
     }
 
+    // TODO(b/138091975) Do not ignore returned Futures if feasible.
+    @SuppressWarnings("FutureReturnValueIgnored")
     PlayerWrapper createPlayerWrapperOfPlayer(@NonNull PlayerWrapper.PlayerCallback callback,
             @Nullable MediaItem item, @Nullable List<MediaItem> playlist) {
         SessionPlayer player = new MediaPlayer(mContext);
diff --git a/paging/common/api/3.0.0-alpha01.txt b/paging/common/api/3.0.0-alpha01.txt
index ac81ae2..4cb7b1d 100644
--- a/paging/common/api/3.0.0-alpha01.txt
+++ b/paging/common/api/3.0.0-alpha01.txt
@@ -150,7 +150,6 @@
     method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
     method public abstract Object? getLastKey();
     method public int getLoadedCount();
-    method public final androidx.paging.PagedSource<?,T> getPagedSource();
     method public int getPositionOffset();
     method public int getSize();
     method public abstract boolean isDetached();
@@ -166,7 +165,6 @@
     property public boolean isImmutable;
     property public abstract Object? lastKey;
     property public int loadedCount;
-    property public final androidx.paging.PagedSource<?,T> pagedSource;
     property public int positionOffset;
     property public int size;
   }
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index ac81ae2..4cb7b1d 100644
--- a/paging/common/api/current.txt
+++ b/paging/common/api/current.txt
@@ -150,7 +150,6 @@
     method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
     method public abstract Object? getLastKey();
     method public int getLoadedCount();
-    method public final androidx.paging.PagedSource<?,T> getPagedSource();
     method public int getPositionOffset();
     method public int getSize();
     method public abstract boolean isDetached();
@@ -166,7 +165,6 @@
     property public boolean isImmutable;
     property public abstract Object? lastKey;
     property public int loadedCount;
-    property public final androidx.paging.PagedSource<?,T> pagedSource;
     property public int positionOffset;
     property public int size;
   }
diff --git a/paging/common/api/restricted_3.0.0-alpha01.txt b/paging/common/api/restricted_3.0.0-alpha01.txt
index 259dc82..976e9a3 100644
--- a/paging/common/api/restricted_3.0.0-alpha01.txt
+++ b/paging/common/api/restricted_3.0.0-alpha01.txt
@@ -155,7 +155,6 @@
     method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
     method public abstract Object? getLastKey();
     method public int getLoadedCount();
-    method public final androidx.paging.PagedSource<?,T> getPagedSource();
     method public int getPositionOffset();
     method public int getSize();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.paging.PagedStorage<T> getStorage();
@@ -173,7 +172,6 @@
     property public boolean isImmutable;
     property public abstract Object? lastKey;
     property public int loadedCount;
-    property public final androidx.paging.PagedSource<?,T> pagedSource;
     property public int positionOffset;
     property public int size;
   }
diff --git a/paging/common/api/restricted_current.txt b/paging/common/api/restricted_current.txt
index 259dc82..976e9a3 100644
--- a/paging/common/api/restricted_current.txt
+++ b/paging/common/api/restricted_current.txt
@@ -155,7 +155,6 @@
     method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
     method public abstract Object? getLastKey();
     method public int getLoadedCount();
-    method public final androidx.paging.PagedSource<?,T> getPagedSource();
     method public int getPositionOffset();
     method public int getSize();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.paging.PagedStorage<T> getStorage();
@@ -173,7 +172,6 @@
     property public boolean isImmutable;
     property public abstract Object? lastKey;
     property public int loadedCount;
-    property public final androidx.paging.PagedSource<?,T> pagedSource;
     property public int positionOffset;
     property public int size;
   }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
index 9d429b1..f880cb6 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -997,16 +997,20 @@
 
     /**
      * The [PagedSource] that provides data to this [PagedList].
+     *
+     * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     val pagedSource: PagedSource<*, T>
 
     /**
      * @throws IllegalStateException if this [PagedList] was instantiated without a
-     * [PagedSourceWrapper] wrapping a backing [DataSource]
+     * wrapping a backing [DataSource]
      */
     @Deprecated(
-        message = "DataSource is deprecated and has been replaced by PagedSource",
-        replaceWith = ReplaceWith("pagedSource")
+        message = "DataSource is deprecated and has been replaced by PagedSource. PagedList " +
+                "offers indirect ways of controlling fetch ('loadAround()', 'retry()') so that " +
+                "you should not need to access the DataSource/PagedSource."
     )
     val dataSource: DataSource<*, T>
         get() {
diff --git a/room/guava/build.gradle b/room/guava/build.gradle
index 4de80a2..a167763 100644
--- a/room/guava/build.gradle
+++ b/room/guava/build.gradle
@@ -33,7 +33,7 @@
     implementation(ARCH_CORE_RUNTIME)
 
     api(SUPPORT_ANNOTATIONS)
-    api(GUAVA_ANDROID)
+    implementation(GUAVA_ANDROID)
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
     androidTestImplementation(TRUTH)
 }
diff --git a/sharetarget/integration-tests/testapp/build.gradle b/sharetarget/integration-tests/testapp/build.gradle
index 4cf7a1f..fd2075d 100644
--- a/sharetarget/integration-tests/testapp/build.gradle
+++ b/sharetarget/integration-tests/testapp/build.gradle
@@ -22,8 +22,8 @@
 }
 
 dependencies {
-    api(project(":core:core"))
-    api(project(":sharetarget"))
+    api("androidx.core:core:1.1.0")
+    api("androidx.sharetarget:sharetarget:1.0.0-alpha02")
     api(project(":appcompat"))
     api(CONSTRAINT_LAYOUT, { transitive = true })
 }
diff --git a/slices/core/api/1.1.0-alpha02.txt b/slices/core/api/1.1.0-alpha02.txt
index bada082..728a150 100644
--- a/slices/core/api/1.1.0-alpha02.txt
+++ b/slices/core/api/1.1.0-alpha02.txt
@@ -64,7 +64,7 @@
   public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
     ctor public SliceProviderWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String?);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String, android.os.Bundle, String?);
   }
 
 }
diff --git a/slices/core/api/current.txt b/slices/core/api/current.txt
index bada082..728a150 100644
--- a/slices/core/api/current.txt
+++ b/slices/core/api/current.txt
@@ -64,7 +64,7 @@
   public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
     ctor public SliceProviderWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String?);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String, android.os.Bundle, String?);
   }
 
 }
diff --git a/slices/core/api/restricted_1.1.0-alpha02.txt b/slices/core/api/restricted_1.1.0-alpha02.txt
index 3cf1dca..878a544 100644
--- a/slices/core/api/restricted_1.1.0-alpha02.txt
+++ b/slices/core/api/restricted_1.1.0-alpha02.txt
@@ -126,7 +126,7 @@
   public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
     ctor public SliceProviderWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String?);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String, android.os.Bundle, String?);
   }
 
   @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class SliceSpec implements androidx.versionedparcelable.VersionedParcelable {
diff --git a/slices/core/api/restricted_current.txt b/slices/core/api/restricted_current.txt
index 3cf1dca..878a544 100644
--- a/slices/core/api/restricted_current.txt
+++ b/slices/core/api/restricted_current.txt
@@ -126,7 +126,7 @@
   public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
     ctor public SliceProviderWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String?);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String, android.os.Bundle, String?);
   }
 
   @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class SliceSpec implements androidx.versionedparcelable.VersionedParcelable {
diff --git a/slices/core/src/main/java/androidx/slice/SliceProvider.java b/slices/core/src/main/java/androidx/slice/SliceProvider.java
index a4b57aa..6164547 100644
--- a/slices/core/src/main/java/androidx/slice/SliceProvider.java
+++ b/slices/core/src/main/java/androidx/slice/SliceProvider.java
@@ -235,6 +235,7 @@
     @Override
     public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
         if (Build.VERSION.SDK_INT < 19) return null;
+        if (extras == null) return null;
         return mCompat != null ? mCompat.call(method, arg, extras) : null;
     }
 
diff --git a/slices/core/src/main/java/androidx/slice/SliceProviderWithCallbacks.java b/slices/core/src/main/java/androidx/slice/SliceProviderWithCallbacks.java
index 5404f79..fffb2d2 100644
--- a/slices/core/src/main/java/androidx/slice/SliceProviderWithCallbacks.java
+++ b/slices/core/src/main/java/androidx/slice/SliceProviderWithCallbacks.java
@@ -59,6 +59,7 @@
     @Nullable
     @Override
     public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+        if (extras == null) return null;
         if (ProviderRelayReceiver.METHOD_PROVIDER_CALLBACK.equals(method)) {
             CallbackHandlerRegistry.sInstance.invokeCallback(getContext(), this, extras);
             return null;
@@ -81,7 +82,7 @@
     @Override
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     public RemoteCallback toRemoteCallback(@NonNull Class<T> cls, @NonNull Context context,
-            @Nullable String authority, @NonNull Bundle args, @Nullable String method) {
+            @NonNull String authority, @NonNull Bundle args, @Nullable String method) {
         if (authority == null) {
             throw new IllegalStateException(
                     "ContentProvider must be attached before creating callbacks");
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/NestedScrollerTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/NestedScrollerTestCase.kt
index 97af4e8..a975728 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/NestedScrollerTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/NestedScrollerTestCase.kt
@@ -31,10 +31,10 @@
 import androidx.ui.layout.Column
 import androidx.ui.layout.CrossAxisAlignment
 import androidx.ui.layout.FlexColumn
-import androidx.ui.layout.HorizontalScroller
+import androidx.ui.foundation.HorizontalScroller
 import androidx.ui.layout.Row
-import androidx.ui.layout.ScrollerPosition
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.VerticalScroller
 import androidx.ui.material.MaterialTheme
 import androidx.ui.material.surface.Surface
 import androidx.ui.test.ComposeTestCase
@@ -65,7 +65,7 @@
     }!!
 
     override fun toggleState() {
-        scrollerPosition.position = if (scrollerPosition.position == 0.px) 10.px else 0.px
+        scrollerPosition.value = if (scrollerPosition.value == 0.px) 10.px else 0.px
         FrameManager.nextFrame()
     }
 
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt
index 40d97a8..65ac2d2 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt
@@ -32,8 +32,8 @@
 import androidx.ui.layout.Column
 import androidx.ui.layout.Container
 import androidx.ui.layout.CrossAxisAlignment
-import androidx.ui.layout.ScrollerPosition
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.VerticalScroller
 import androidx.ui.painting.Paint
 import androidx.ui.painting.PaintingStyle
 import androidx.ui.test.ComposeTestCase
@@ -75,7 +75,7 @@
     }!!
 
     override fun toggleState() {
-        scrollerPosition.position = if (scrollerPosition.position == 0.px) 10.px else 0.px
+        scrollerPosition.value = if (scrollerPosition.value == 0.px) 10.px else 0.px
         FrameManager.nextFrame()
     }
 
diff --git a/ui/ui-foundation/api/1.0.0-alpha01.txt b/ui/ui-foundation/api/1.0.0-alpha01.txt
index 782f0c2..522a82c 100644
--- a/ui/ui-foundation/api/1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/1.0.0-alpha01.txt
@@ -42,6 +42,27 @@
     method public void vertical(int column, kotlin.ranges.IntRange rows = 0 <other> rowCount, androidx.ui.foundation.shape.border.Border border = defaultBorder);
   }
 
+  public final class ScrollerKt {
+    ctor public ScrollerKt();
+    method public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({ 
+    <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+    method public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({ 
+    <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+  }
+
+  public final class ScrollerPosition {
+    ctor public ScrollerPosition();
+    method public androidx.ui.core.Px getValue();
+    method public void scrollBy(androidx.ui.core.Px value);
+    method public void scrollTo(androidx.ui.core.Px value);
+    method public void setValue(androidx.ui.core.Px p);
+    method public void smoothScrollBy(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+    method public void smoothScrollTo(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+    property public final androidx.ui.core.Px value;
+  }
+
   public final class SimpleImageKt {
     ctor public SimpleImageKt();
     method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
@@ -71,13 +92,13 @@
 package androidx.ui.foundation.animation {
 
   public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+    ctor public AnimatedFloatDragController(androidx.animation.AnimatedFloat animatedFloat, androidx.ui.foundation.animation.FlingConfig? flingConfig);
     ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
     method public androidx.animation.AnimatedFloat getAnimatedFloat();
     method public float getCurrentValue();
     method public void onDrag(float target);
     method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
     method public void setBounds(float min, float max);
-    property public final androidx.animation.AnimatedFloat animatedFloat;
     property public float currentValue;
   }
 
diff --git a/ui/ui-foundation/api/current.txt b/ui/ui-foundation/api/current.txt
index 782f0c2..522a82c 100644
--- a/ui/ui-foundation/api/current.txt
+++ b/ui/ui-foundation/api/current.txt
@@ -42,6 +42,27 @@
     method public void vertical(int column, kotlin.ranges.IntRange rows = 0 <other> rowCount, androidx.ui.foundation.shape.border.Border border = defaultBorder);
   }
 
+  public final class ScrollerKt {
+    ctor public ScrollerKt();
+    method public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({ 
+    <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+    method public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({ 
+    <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+  }
+
+  public final class ScrollerPosition {
+    ctor public ScrollerPosition();
+    method public androidx.ui.core.Px getValue();
+    method public void scrollBy(androidx.ui.core.Px value);
+    method public void scrollTo(androidx.ui.core.Px value);
+    method public void setValue(androidx.ui.core.Px p);
+    method public void smoothScrollBy(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+    method public void smoothScrollTo(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+    property public final androidx.ui.core.Px value;
+  }
+
   public final class SimpleImageKt {
     ctor public SimpleImageKt();
     method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
@@ -71,13 +92,13 @@
 package androidx.ui.foundation.animation {
 
   public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+    ctor public AnimatedFloatDragController(androidx.animation.AnimatedFloat animatedFloat, androidx.ui.foundation.animation.FlingConfig? flingConfig);
     ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
     method public androidx.animation.AnimatedFloat getAnimatedFloat();
     method public float getCurrentValue();
     method public void onDrag(float target);
     method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
     method public void setBounds(float min, float max);
-    property public final androidx.animation.AnimatedFloat animatedFloat;
     property public float currentValue;
   }
 
diff --git a/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt b/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
index 782f0c2..522a82c 100644
--- a/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
@@ -42,6 +42,27 @@
     method public void vertical(int column, kotlin.ranges.IntRange rows = 0 <other> rowCount, androidx.ui.foundation.shape.border.Border border = defaultBorder);
   }
 
+  public final class ScrollerKt {
+    ctor public ScrollerKt();
+    method public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({ 
+    <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+    method public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({ 
+    <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+  }
+
+  public final class ScrollerPosition {
+    ctor public ScrollerPosition();
+    method public androidx.ui.core.Px getValue();
+    method public void scrollBy(androidx.ui.core.Px value);
+    method public void scrollTo(androidx.ui.core.Px value);
+    method public void setValue(androidx.ui.core.Px p);
+    method public void smoothScrollBy(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+    method public void smoothScrollTo(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+    property public final androidx.ui.core.Px value;
+  }
+
   public final class SimpleImageKt {
     ctor public SimpleImageKt();
     method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
@@ -71,13 +92,13 @@
 package androidx.ui.foundation.animation {
 
   public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+    ctor public AnimatedFloatDragController(androidx.animation.AnimatedFloat animatedFloat, androidx.ui.foundation.animation.FlingConfig? flingConfig);
     ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
     method public androidx.animation.AnimatedFloat getAnimatedFloat();
     method public float getCurrentValue();
     method public void onDrag(float target);
     method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
     method public void setBounds(float min, float max);
-    property public final androidx.animation.AnimatedFloat animatedFloat;
     property public float currentValue;
   }
 
diff --git a/ui/ui-foundation/api/restricted_current.txt b/ui/ui-foundation/api/restricted_current.txt
index 782f0c2..522a82c 100644
--- a/ui/ui-foundation/api/restricted_current.txt
+++ b/ui/ui-foundation/api/restricted_current.txt
@@ -42,6 +42,27 @@
     method public void vertical(int column, kotlin.ranges.IntRange rows = 0 <other> rowCount, androidx.ui.foundation.shape.border.Border border = defaultBorder);
   }
 
+  public final class ScrollerKt {
+    ctor public ScrollerKt();
+    method public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({ 
+    <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+    method public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({ 
+    <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+  }
+
+  public final class ScrollerPosition {
+    ctor public ScrollerPosition();
+    method public androidx.ui.core.Px getValue();
+    method public void scrollBy(androidx.ui.core.Px value);
+    method public void scrollTo(androidx.ui.core.Px value);
+    method public void setValue(androidx.ui.core.Px p);
+    method public void smoothScrollBy(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+    method public void smoothScrollTo(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+    property public final androidx.ui.core.Px value;
+  }
+
   public final class SimpleImageKt {
     ctor public SimpleImageKt();
     method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
@@ -71,13 +92,13 @@
 package androidx.ui.foundation.animation {
 
   public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+    ctor public AnimatedFloatDragController(androidx.animation.AnimatedFloat animatedFloat, androidx.ui.foundation.animation.FlingConfig? flingConfig);
     ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
     method public androidx.animation.AnimatedFloat getAnimatedFloat();
     method public float getCurrentValue();
     method public void onDrag(float target);
     method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
     method public void setBounds(float min, float max);
-    property public final androidx.animation.AnimatedFloat animatedFloat;
     property public float currentValue;
   }
 
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml b/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml
index 4609982..2bdca6b 100644
--- a/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml
@@ -21,11 +21,27 @@
     <application>
 
         <activity android:name=".AnimatedDraggableActivity"
-            android:configChanges="orientation|screenSize"
-            android:label="Foundation/AnimatedDraggable">
+                  android:configChanges="orientation|screenSize"
+                  android:label="Foundation/AnimatedDraggable">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="androidx.ui.demos.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".VerticalScrollerActivity"
+                  android:configChanges="orientation|screenSize"
+                  android:label="Foundation/VerticalScroller">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="androidx.ui.demos.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".HorizontalScrollerActivity"
+                  android:configChanges="orientation|screenSize"
+                  android:label="Foundation/HorizontalScroller with controls">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="androidx.ui.demos.SAMPLE_CODE"/>
             </intent-filter>
         </activity>
 
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/HorizontalScrollerActivity.kt b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/HorizontalScrollerActivity.kt
new file mode 100644
index 0000000..6b4d4b9
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/HorizontalScrollerActivity.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.foundation.demos
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.composer
+import androidx.ui.core.setContent
+import androidx.ui.foundation.samples.ControlledHorizontalScrollerSample
+import androidx.ui.layout.Wrap
+
+class HorizontalScrollerActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            Wrap {
+                ControlledHorizontalScrollerSample()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/VerticalScrollerActivity.kt b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/VerticalScrollerActivity.kt
new file mode 100644
index 0000000..defa1f8
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/VerticalScrollerActivity.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.foundation.demos
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.composer
+import androidx.ui.core.setContent
+import androidx.ui.foundation.samples.VerticalScrollerSample
+import androidx.ui.layout.Wrap
+
+class VerticalScrollerActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            Wrap {
+                VerticalScrollerSample()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt
new file mode 100644
index 0000000..30d8e63
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.foundation.samples
+
+import android.util.Log
+import androidx.annotation.Sampled
+import androidx.compose.Composable
+import androidx.compose.State
+import androidx.compose.composer
+import androidx.compose.memo
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.Draw
+import androidx.ui.core.PxSize
+import androidx.ui.core.Text
+import androidx.ui.core.dp
+import androidx.ui.core.px
+import androidx.ui.core.setContent
+import androidx.ui.core.sp
+import androidx.ui.core.toRect
+import androidx.ui.foundation.Clickable
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.foundation.HorizontalScroller
+import androidx.ui.foundation.VerticalScroller
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Column
+import androidx.ui.layout.Padding
+import androidx.ui.layout.Row
+import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.shape.DrawShape
+import androidx.ui.foundation.shape.RectangleShape
+import androidx.ui.layout.Alignment
+import androidx.ui.layout.Container
+import androidx.ui.layout.EdgeInsets
+import androidx.ui.layout.FlexColumn
+import androidx.ui.layout.HeightSpacer
+import androidx.ui.layout.Stack
+import androidx.ui.layout.Table
+import androidx.ui.layout.Wrap
+import androidx.ui.painting.Canvas
+import androidx.ui.painting.Paint
+import androidx.ui.text.ParagraphStyle
+import androidx.ui.text.TextStyle
+
+private val colors = listOf(
+    Color(0xFFffd7d7.toInt()),
+    Color(0xFFffe9d6.toInt()),
+    Color(0xFFfffbd0.toInt()),
+    Color(0xFFe3ffd9.toInt()),
+    Color(0xFFd0fff8.toInt())
+)
+
+private val phrases = listOf(
+    "Easy As Pie",
+    "Wouldn't Harm a Fly",
+    "No-Brainer",
+    "Keep On Truckin'",
+    "An Arm and a Leg",
+    "Down To Earth",
+    "Under the Weather",
+    "Up In Arms",
+    "Cup Of Joe",
+    "Not the Sharpest Tool in the Shed",
+    "Ring Any Bells?",
+    "Son of a Gun",
+    "Hard Pill to Swallow",
+    "Close But No Cigar",
+    "Beating a Dead Horse",
+    "If You Can't Stand the Heat, Get Out of the Kitchen",
+    "Cut To The Chase",
+    "Heads Up",
+    "Goody Two-Shoes",
+    "Fish Out Of Water",
+    "Cry Over Spilt Milk",
+    "Elephant in the Room",
+    "There's No I in Team",
+    "Poke Fun At",
+    "Talk the Talk",
+    "Know the Ropes",
+    "Fool's Gold",
+    "It's Not Brain Surgery",
+    "Fight Fire With Fire",
+    "Go For Broke"
+)
+
+@Sampled
+@Composable
+fun VerticalScrollerSample() {
+    val style = TextStyle(fontSize = 30.sp)
+    // Scroller will be clipped to this padding
+    Padding(padding = 10.dp) {
+        VerticalScroller {
+            Column {
+                phrases.forEach { phrase ->
+                    Text(text = phrase, style = style)
+                }
+            }
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun SimpleHorizontalScrollerSample() {
+    HorizontalScroller {
+        Row {
+            repeat(100) { index ->
+                Square(index)
+            }
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun ControlledHorizontalScrollerSample() {
+    // Create and own ScrollerPosition to call `smoothScrollTo` later
+    val position = +memo { ScrollerPosition() }
+    val scrollable = +state { true }
+    Column {
+        HorizontalScroller(scrollerPosition = position, isScrollable = scrollable.value) {
+            Row {
+                repeat(1000) { index ->
+                    Square(index)
+                }
+            }
+        }
+        // Controls that will call `smoothScrollTo`, `scrollTo` or toggle `scrollable` state
+        ScrollControl(position, scrollable)
+    }
+}
+
+@Composable
+private fun Square(index: Int) {
+    Container(width = 75.dp, height = 200.dp) {
+        DrawShape(RectangleShape, colors[index % colors.size])
+        Text(index.toString())
+    }
+}
+
+@Composable
+private fun ScrollControl(position: ScrollerPosition, scrollable: State<Boolean>) {
+    Padding(top = 20.dp) {
+        Table(3, childAlignment = Alignment.Center) {
+            tableRow {
+                Text("Scroll")
+                SquareButton("< -", Color.Red) {
+                    position.scrollTo(position.value - 1000.px)
+                }
+                SquareButton("--- >", Color.Green) {
+                    position.scrollBy(10000.px)
+                }
+            }
+            tableRow {
+                Text("Smooth Scroll")
+                SquareButton("< -", Color.Red) {
+                    position.smoothScrollTo(position.value - 1000.px)
+                }
+                SquareButton("--- >", Color.Green) {
+                    position.smoothScrollBy(10000.px)
+                }
+            }
+            tableRow {
+                SquareButton("Scroll: ${scrollable.value}") {
+                    scrollable.value = !scrollable.value
+                }
+                // empty container to fill table
+                Container { }
+                Container { }
+            }
+        }
+    }
+}
+
+@Composable
+private fun SquareButton(text: String, color: Color = Color.LightGray, onClick: () -> Unit) {
+    Clickable(onClick = onClick) {
+        Padding(5.dp) {
+            Container(height = 60.dp, width = 120.dp) {
+                DrawShape(RectangleShape, color)
+                Text(text, style = TextStyle(fontSize = 20.sp))
+            }
+        }
+    }
+}
diff --git a/ui/ui-foundation/src/androidTest/AndroidManifest.xml b/ui/ui-foundation/src/androidTest/AndroidManifest.xml
index 8c0fd1c..bacf210 100644
--- a/ui/ui-foundation/src/androidTest/AndroidManifest.xml
+++ b/ui/ui-foundation/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,14 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<manifest package="androidx.ui.foundation" xmlns:android="http://schemas.android.com/apk/res/android">
-     <application>
+<manifest package="androidx.ui.foundation"
+          xmlns:tools="http://schemas.android.com/tools"
+          xmlns:android="http://schemas.android.com/apk/res/android">
+     <application
+         android:requestLegacyExternalStorage="true"
+         android:debuggable="false"
+         tools:ignore="HardcodedDebugMode"
+         tools:replace="android:debuggable">
         <activity
             android:name="androidx.ui.test.android.DefaultTestActivity"
             android:theme="@style/TestTheme"/>
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
similarity index 65%
rename from ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerTest.kt
rename to ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
index b15be36..9f78d2c 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
@@ -13,11 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package androidx.ui.layout.test
+package androidx.ui.foundation
 
+import android.app.Activity
 import android.graphics.Bitmap
 import android.os.Build
+import android.os.Handler
 import android.view.PixelCopy
+import android.view.View
+import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import androidx.test.filters.SmallTest
 import androidx.ui.core.Draw
@@ -34,20 +38,21 @@
 import androidx.ui.layout.Container
 import androidx.ui.layout.CrossAxisAlignment
 import androidx.ui.layout.DpConstraints
-import androidx.ui.layout.ScrollerPosition
-import androidx.ui.layout.VerticalScroller
 import androidx.ui.graphics.Color
 import androidx.ui.painting.Paint
 import androidx.ui.painting.PaintingStyle
 import androidx.compose.composer
 import androidx.test.filters.SdkSuppress
+import androidx.ui.core.AndroidCraneView
 import androidx.ui.core.setContent
-import androidx.ui.layout.HorizontalScroller
 import androidx.ui.layout.Row
+import androidx.ui.test.android.AndroidComposeTestRule
+import androidx.ui.test.createComposeRule
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -56,7 +61,16 @@
 
 @SmallTest
 @RunWith(JUnit4::class)
-class ScrollerTest : LayoutTest() {
+class ScrollerTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    // TODO(malkov/pavlis) : some tests here require activity access as we need
+    // to take screen's bitmap, abstract it better
+    val activity
+        get() = (composeTestRule as AndroidComposeTestRule).activityTestRule.activity
+
     val colors = listOf(
         Color(alpha = 0xFF, red = 0xFF, green = 0, blue = 0),
         Color(alpha = 0xFF, red = 0xFF, green = 0xA5, blue = 0),
@@ -69,10 +83,14 @@
     )
 
     var drawLatch = CountDownLatch(1)
+    lateinit var handler: Handler
 
     @Before
     fun setupDrawLatch() {
         drawLatch = CountDownLatch(1)
+        composeTestRule.runOnUiThread {
+            handler = Handler()
+        }
     }
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@@ -99,8 +117,6 @@
         val changeListener = ScrollerChangeListener(scrollerPosition)
         composeVerticalScroller(scrollerPosition, changeListener, height = 30.ipx)
 
-        changeListener.waitForChange()
-
         validateVerticalScroller(0, 30)
 
         // The 'draw' method will no longer be called because only the position
@@ -112,13 +128,13 @@
                 latch.countDown()
             }
         }
-        runOnUiThread {
+        composeTestRule.runOnUiThread {
             activity.window.decorView.viewTreeObserver.addOnDrawListener(onDrawListener)
             assertEquals(10.px, changeListener.maxPosition)
-            scrollerPosition.position = 10.px
+            scrollerPosition.value = 10.px
         }
         assertTrue(latch.await(1, TimeUnit.SECONDS))
-        runOnUiThread {
+        composeTestRule.runOnUiThread {
             activity.window.decorView.viewTreeObserver.removeOnDrawListener(onDrawListener)
         }
         validateVerticalScroller(10, 30)
@@ -148,8 +164,6 @@
         val changeListener = ScrollerChangeListener(scrollerPosition)
         composeHorizontalScroller(scrollerPosition, changeListener, width = 30.ipx)
 
-        changeListener.waitForChange()
-
         validateHorizontalScroller(0, 30)
 
         // The 'draw' method will no longer be called because only the position
@@ -161,13 +175,13 @@
                 latch.countDown()
             }
         }
-        runOnUiThread {
+        composeTestRule.runOnUiThread {
             activity.window.decorView.viewTreeObserver.addOnDrawListener(onDrawListener)
             assertEquals(10.px, changeListener.maxPosition)
-            scrollerPosition.position = 10.px
+            scrollerPosition.value = 10.px
         }
         assertTrue(latch.await(1, TimeUnit.SECONDS))
-        runOnUiThread {
+        composeTestRule.runOnUiThread {
             activity.window.decorView.viewTreeObserver.removeOnDrawListener(onDrawListener)
         }
         validateHorizontalScroller(10, 30)
@@ -175,95 +189,89 @@
 
     private fun composeVerticalScroller(
         scrollerPosition: ScrollerPosition = ScrollerPosition(),
-        onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
-            scrollerPosition.position = position
+        onScrollChanged: (position: Px, max: Px) -> Unit = { position, _ ->
+            scrollerPosition.value = position
         },
         height: IntPx = 40.ipx
     ) {
         // We assume that the height of the device is more than 45 px
-        withDensity(density) {
+        withDensity(composeTestRule.density) {
             val constraints = DpConstraints.tightConstraints(45.px.toDp(), height.toDp())
-            val runnable: Runnable = object : Runnable {
-                override fun run() {
-                    activity.setContent {
-                        Align(alignment = Alignment.TopLeft) {
-                            ConstrainedBox(constraints = constraints) {
-                                VerticalScroller(
-                                    scrollerPosition = scrollerPosition,
-                                    onScrollChanged = onScrollChanged
-                                ) {
-                                    Column(crossAxisAlignment = CrossAxisAlignment.Start) {
-                                        colors.forEach { color ->
-                                            Container(
-                                                height = 5.px.toDp(),
-                                                width = 45.px.toDp()
-                                            ) {
-                                                Draw { canvas, parentSize ->
-                                                    val paint = Paint()
-                                                    paint.color = color
-                                                    paint.style = PaintingStyle.fill
-                                                    canvas.drawRect(parentSize.toRect(), paint)
-                                                }
+            composeTestRule.runOnUiThread {
+                activity.setContent {
+                    Align(alignment = Alignment.TopLeft) {
+                        ConstrainedBox(constraints = constraints) {
+                            VerticalScroller(
+                                scrollerPosition = scrollerPosition,
+                                onScrollPositionChanged = onScrollChanged
+                            ) {
+                                Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+                                    colors.forEach { color ->
+                                        Container(
+                                            height = 5.px.toDp(),
+                                            width = 45.px.toDp()
+                                        ) {
+                                            Draw { canvas, parentSize ->
+                                                val paint = Paint()
+                                                paint.color = color
+                                                paint.style = PaintingStyle.fill
+                                                canvas.drawRect(parentSize.toRect(), paint)
                                             }
                                         }
                                     }
                                 }
-                                Draw { _, _ ->
-                                    drawLatch.countDown()
-                                }
+                            }
+                            Draw { _, _ ->
+                                drawLatch.countDown()
                             }
                         }
                     }
                 }
             }
-            activityTestRule.runOnUiThread(runnable)
         }
     }
 
     private fun composeHorizontalScroller(
         scrollerPosition: ScrollerPosition = ScrollerPosition(),
-        onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
-            scrollerPosition.position = position
+        onScrollChanged: (position: Px, max: Px) -> Unit = { position, _ ->
+            scrollerPosition.value = position
         },
         width: IntPx = 40.ipx
     ) {
         // We assume that the height of the device is more than 45 px
-        withDensity(density) {
+        withDensity(composeTestRule.density) {
             val constraints = DpConstraints.tightConstraints(width.toDp(), 45.px.toDp())
-            val runnable: Runnable = object : Runnable {
-                override fun run() {
-                    activity.setContent {
-                        Align(alignment = Alignment.TopLeft) {
-                            ConstrainedBox(constraints = constraints) {
-                                HorizontalScroller(
-                                    scrollerPosition = scrollerPosition,
-                                    onScrollChanged = onScrollChanged
-                                ) {
-                                    Row(crossAxisAlignment = CrossAxisAlignment.Start) {
-                                        colors.forEach { color ->
-                                            Container(
-                                                width = 5.px.toDp(),
-                                                height = 45.px.toDp()
-                                            ) {
-                                                Draw { canvas, parentSize ->
-                                                    val paint = Paint()
-                                                    paint.color = color
-                                                    paint.style = PaintingStyle.fill
-                                                    canvas.drawRect(parentSize.toRect(), paint)
-                                                }
+            composeTestRule.runOnUiThread {
+                activity.setContent {
+                    Align(alignment = Alignment.TopLeft) {
+                        ConstrainedBox(constraints = constraints) {
+                            HorizontalScroller(
+                                scrollerPosition = scrollerPosition,
+                                onScrollPositionChanged = onScrollChanged
+                            ) {
+                                Row(crossAxisAlignment = CrossAxisAlignment.Start) {
+                                    colors.forEach { color ->
+                                        Container(
+                                            width = 5.px.toDp(),
+                                            height = 45.px.toDp()
+                                        ) {
+                                            Draw { canvas, parentSize ->
+                                                val paint = Paint()
+                                                paint.color = color
+                                                paint.style = PaintingStyle.fill
+                                                canvas.drawRect(parentSize.toRect(), paint)
                                             }
                                         }
                                     }
                                 }
-                                Draw { _, _ ->
-                                    drawLatch.countDown()
-                                }
+                            }
+                            Draw { _, _ ->
+                                drawLatch.countDown()
                             }
                         }
                     }
                 }
             }
-            activityTestRule.runOnUiThread(runnable)
         }
     }
 
@@ -315,16 +323,6 @@
         }
     }
 
-    // We only need this because IR compiler doesn't like converting lambdas to Runnables
-    private fun runOnUiThread(block: () -> Unit) {
-        @Suppress("ObjectLiteralToLambda") val runnable: Runnable = object : Runnable {
-            override fun run() {
-                block()
-            }
-        }
-        activityTestRule.runOnUiThread(runnable)
-    }
-
     private fun waitAndScreenShot(): Bitmap {
         val view = findAndroidCraneView()
         waitForDraw(view)
@@ -365,7 +363,7 @@
                 changeCalls++
                 lock.notify()
             }
-            scrollerPosition.position = position
+            scrollerPosition.value = position
             this.maxPosition = maxPosition
         }
 
@@ -379,4 +377,42 @@
             }
         }
     }
+
+    // TODO(malkov): ALL below is copypaste from LayoutTest as this test in ui-foundation now
+
+    internal fun findAndroidCraneView(): AndroidCraneView {
+        val contentViewGroup = activity.findViewById<ViewGroup>(android.R.id.content)
+        return findAndroidCraneView(contentViewGroup)!!
+    }
+
+    internal fun findAndroidCraneView(parent: ViewGroup): AndroidCraneView? {
+        for (index in 0 until parent.childCount) {
+            val child = parent.getChildAt(index)
+            if (child is AndroidCraneView) {
+                return child
+            } else if (child is ViewGroup) {
+                val craneView = findAndroidCraneView(child)
+                if (craneView != null) {
+                    return craneView
+                }
+            }
+        }
+        return null
+    }
+
+    internal fun waitForDraw(view: View) {
+        val viewDrawLatch = CountDownLatch(1)
+        val listener = object : ViewTreeObserver.OnDrawListener {
+            override fun onDraw() {
+                viewDrawLatch.countDown()
+            }
+        }
+        view.post(object : Runnable {
+            override fun run() {
+                view.viewTreeObserver.addOnDrawListener(listener)
+                view.invalidate()
+            }
+        })
+        assertTrue(viewDrawLatch.await(1, TimeUnit.SECONDS))
+    }
 }
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
new file mode 100644
index 0000000..3849680
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.foundation
+
+import androidx.animation.AnimatedFloat
+import androidx.animation.ExponentialDecay
+import androidx.animation.ValueHolder
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.Model
+import androidx.compose.memo
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.Clip
+import androidx.ui.core.Constraints
+import androidx.ui.core.IntPx
+import androidx.ui.core.Layout
+import androidx.ui.core.Px
+import androidx.ui.core.RepaintBoundary
+import androidx.ui.core.gesture.PressGestureDetector
+import androidx.ui.core.ipx
+import androidx.ui.core.min
+import androidx.ui.core.px
+import androidx.ui.core.round
+import androidx.ui.core.toPx
+import androidx.ui.foundation.animation.AnimatedFloatDragController
+import androidx.ui.foundation.animation.FlingConfig
+import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.DragValueController
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.foundation.shape.RectangleShape
+import androidx.ui.layout.Constraints
+import androidx.ui.layout.Container
+import androidx.ui.lerp
+
+/**
+ * This is the state of a [VerticalScroller] and [HorizontalScroller] that
+ * allows the developer to change the scroll position.
+ * [value] must be between `0` and `maxPosition` in `onScrollPositionChanged`'s `maxPosition`
+ * parameter.
+ */
+@Model
+class ScrollerPosition {
+
+    /**
+     * The amount of scrolling, between `0` and `maxPosition` in `onScrollPositionChanged`'s
+     * `maxPosition` parameter.
+     */
+    var value: Px = 0.px
+
+    /**
+     * Smooth scroll to position in pixels
+     *
+     * @param value target value to smooth scroll to
+     */
+    // TODO (malkov/tianliu) : think about allowing to scroll with custom animation timings/curves
+    fun smoothScrollTo(value: Px, onFinished: (isCancelled: Boolean) -> Unit = {}) {
+        controller.animatedFloat.animateTo(-value.value, onFinished)
+    }
+
+    /**
+     * Smooth scroll by some amount of pixels
+     *
+     * @param value delta to scroll by
+     */
+    fun smoothScrollBy(value: Px, onFinished: (isCancelled: Boolean) -> Unit = {}) {
+        smoothScrollTo(this.value + value, onFinished)
+    }
+
+    /**
+     * Instantly jump to position in pixels
+     *
+     * @param value target value to jump to
+     */
+    fun scrollTo(value: Px) {
+        controller.onDrag(-value.value)
+    }
+
+    /**
+     * Instantly jump by some amount of pixels
+     *
+     * @param value delta to jump by
+     */
+    fun scrollBy(value: Px) {
+        scrollTo(this.value + value)
+    }
+
+    // TODO (malkov/tianliu): Open this for customization
+    private val flingConfig = FlingConfig(
+        decayAnimation = ExponentialDecay(
+            frictionMultiplier = ScrollerDefaultFriction,
+            absVelocityThreshold = ScrollerVelocityThreshold
+        )
+    )
+
+    internal val controller =
+        ScrollerDragValueController({ lh.lambda.invoke(-it) }, flingConfig)
+
+    // This is needed to take instant value we're currently dragging
+    // and avoid reading @Model var field
+    internal val instantValue
+        get() = -controller.currentValue
+
+    // This is needed to avoid var (read of which will cause unnecessary recompose in Scroller)
+    internal val lh = LambdaHolder { value = it.px }
+}
+
+/**
+ * A container that composes all of its contents and lays it out, fitting the width of the child.
+ * If the child's height is less than the [Constraints.maxHeight], the child's height is used,
+ * or the [Constraints.maxHeight] otherwise. If the contents don't fit the height, the drag gesture
+ * allows scrolling its content vertically. The contents of the VerticalScroller are clipped to
+ * the VerticalScroller's bounds.
+ *
+ * @sample androidx.ui.foundation.samples.VerticalScrollerSample
+ *
+ * @param scrollerPosition state of this Scroller that holds current scroll position and provides
+ * user with useful methods like smooth scrolling
+ * @param onScrollPositionChanged callback to be invoked when scroll position is about to be
+ * changed or max bound of scrolling has changed
+ * @param isScrollable param to enabled or disable touch input scrolling, default is true
+ */
+@Composable
+fun VerticalScroller(
+    scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
+    onScrollPositionChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
+        scrollerPosition.value = position
+    },
+    isScrollable: Boolean = true,
+    @Children child: @Composable() () -> Unit
+) {
+    Scroller(scrollerPosition, onScrollPositionChanged, true, isScrollable, child)
+}
+
+/**
+ * A container that composes all of its contents and lays it out, fitting the height of the child.
+ * If the child's width is less than the [Constraints.maxWidth], the child's width is used,
+ * or the [Constraints.maxWidth] otherwise. If the contents don't fit the width, the drag gesture
+ * allows scrolling its content horizontally. The contents of the HorizontalScroller are clipped to
+ * the HorizontalScroller's bounds.
+ *
+ * @sample androidx.ui.foundation.samples.SimpleHorizontalScrollerSample
+ *
+ * If you want to control scrolling position from the code, e.g smooth scroll to position,
+ * you must own memorized instance of [ScrollerPosition] and then use it to call `scrollTo...`
+ * functions on it. Same tactic can be applied to the [VerticalScroller]
+ *
+ * @sample androidx.ui.foundation.samples.ControlledHorizontalScrollerSample
+ *
+ * @param scrollerPosition state of this Scroller that holds current scroll position and provides
+ * user with useful methods like smooth scrolling
+ * @param onScrollPositionChanged callback to be invoked when scroll position is about to be
+ * changed or max bound of scrolling has changed
+ * @param isScrollable param to enabled or disable touch input scrolling, default is true
+ */
+@Composable
+fun HorizontalScroller(
+    scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
+    onScrollPositionChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
+        scrollerPosition.value = position
+    },
+    isScrollable: Boolean = true,
+    @Children child: @Composable() () -> Unit
+) {
+    Scroller(scrollerPosition, onScrollPositionChanged, false, isScrollable, child)
+}
+
+@Composable
+private fun Scroller(
+    scrollerPosition: ScrollerPosition,
+    onScrollPositionChanged: (position: Px, maxPosition: Px) -> Unit,
+    isVertical: Boolean,
+    isScrollable: Boolean,
+    @Children child: @Composable() () -> Unit
+) {
+    val maxPosition = +state { Px.Infinity }
+    val direction = if (isVertical) DragDirection.Vertical else DragDirection.Horizontal
+    val controller = +memo(isScrollable) {
+        if (isScrollable) {
+            scrollerPosition.controller
+        } else {
+            StubController(scrollerPosition.instantValue).also {
+                scrollerPosition.scrollTo(scrollerPosition.instantValue.px)
+            }
+        }
+    }
+    scrollerPosition.lh.lambda = { onScrollPositionChanged(it.px, maxPosition.value) }
+    PressGestureDetector(onPress = { scrollerPosition.scrollTo(scrollerPosition.value) }) {
+        Draggable(
+            dragDirection = direction,
+            minValue = -maxPosition.value.value,
+            maxValue = 0f,
+            valueController = controller
+        ) {
+            ScrollerLayout(
+                scrollerPosition = scrollerPosition,
+                maxPosition = maxPosition.value,
+                onMaxPositionChanged = {
+                    maxPosition.value = it
+                    onScrollPositionChanged(scrollerPosition.value, it)
+                },
+                isVertical = isVertical,
+                child = child
+            )
+        }
+    }
+}
+
+@Composable
+private fun ScrollerLayout(
+    scrollerPosition: ScrollerPosition,
+    maxPosition: Px,
+    onMaxPositionChanged: (Px) -> Unit,
+    isVertical: Boolean,
+    child: @Composable() () -> Unit
+) {
+    Layout(children = {
+        Clip(RectangleShape) {
+            Container {
+                RepaintBoundary {
+                    child()
+                }
+            }
+        }
+    }) { measurables, constraints ->
+        if (measurables.size > 1) {
+            throw IllegalStateException("Only one child is allowed in a VerticalScroller")
+        }
+        val childConstraints = constraints.copy(
+            maxHeight = if (isVertical) IntPx.Infinity else constraints.maxHeight,
+            maxWidth = if (isVertical) constraints.maxWidth else IntPx.Infinity
+        )
+        val childMeasurable = measurables.firstOrNull()
+        val placeable = childMeasurable?.measure(childConstraints)
+        val width: IntPx
+        val height: IntPx
+        if (placeable == null) {
+            width = constraints.minWidth
+            height = constraints.minHeight
+        } else {
+            width = min(placeable.width, constraints.maxWidth)
+            height = min(placeable.height, constraints.maxHeight)
+        }
+        layout(width, height) {
+            val childHeight = placeable?.height?.toPx() ?: 0.px
+            val childWidth = placeable?.width?.toPx() ?: 0.px
+            val scrollHeight = childHeight - height.toPx()
+            val scrollWidth = childWidth - width.toPx()
+            val side = if (isVertical) scrollHeight else scrollWidth
+            if (side != 0.px && side != maxPosition) {
+                onMaxPositionChanged(side)
+            }
+            val xOffset = if (isVertical) 0.ipx else -scrollerPosition.value.round()
+            val yOffset = if (isVertical) -scrollerPosition.value.round() else 0.ipx
+            placeable?.place(xOffset, yOffset)
+        }
+    }
+}
+
+private fun ScrollerDragValueController(
+    onValueChanged: (Float) -> Unit,
+    flingConfig: FlingConfig? = null
+) = AnimatedFloatDragController(
+    AnimatedFloat(ScrollPositionValueHolder(0f, onValueChanged)),
+    flingConfig
+)
+
+private class ScrollPositionValueHolder(
+    var current: Float,
+    val onValueChanged: (Float) -> Unit
+) : ValueHolder<Float> {
+    override val interpolator: (start: Float, end: Float, fraction: Float) -> Float = ::lerp
+    override var value: Float
+        get() = current
+        set(value) {
+            current = value
+            onValueChanged(value)
+        }
+}
+
+private fun StubController(value: Float) = object : DragValueController {
+    override val currentValue: Float
+        get() = value
+
+    override fun onDrag(target: Float) {}
+
+    override fun onDragEnd(velocity: Float, onValueSettled: (Float) -> Unit) {}
+
+    override fun setBounds(min: Float, max: Float) {}
+}
+
+internal data class LambdaHolder(var lambda: (Float) -> Unit)
+
+private val ScrollerDefaultFriction = 0.35f
+private val ScrollerVelocityThreshold = 1000f
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
index c47f27e..8198e44 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
@@ -29,16 +29,24 @@
  * It makes it possible to have animation support for [Draggable] composable
  * as well as to have fling animation after drag has ended, which is defined by [FlingConfig]
  *
- * @param initialValue initial value for AnimatedFloat to set it up
+ * @param animatedFloat instance of AnimatedFloat to control
  * @param flingConfig sets behavior of the fling after drag has ended.
  * Default is null, which means no fling will occur no matter the velocity
  */
 class AnimatedFloatDragController(
-    initialValue: Float,
+    val animatedFloat: AnimatedFloat,
     private val flingConfig: FlingConfig? = null
 ) : DragValueController {
 
-    val animatedFloat = AnimatedFloat(AnimValueHolder(initialValue, ::lerp))
+    /**
+     * Construct controller that creates and owns AnimatedFloat instance
+     *
+     * @param initialValue initial value for AnimatedFloat to set it up
+     * @param flingConfig sets behavior of the fling after drag has ended.
+     * Default is null, which means no fling will occur no matter the velocity
+     */
+    constructor(initialValue: Float, flingConfig: FlingConfig?)
+            : this(AnimatedFloat(AnimValueHolder(initialValue, ::lerp)), flingConfig)
 
     override val currentValue
         get() = animatedFloat.value
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
index 4ce2cf1..bf850804 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
@@ -205,7 +205,7 @@
             accessibilityLabel = text.text
         }
     ) {
-        val textPainter = +memo(
+        val textDelegate = +memo(
             text,
             mergedStyle,
             paragraphStyle,
@@ -232,17 +232,17 @@
             OnPositioned(onPositioned = { layoutCoordinates.value = it })
             Draw { canvas, _ ->
                 internalSelection.value?.let {
-                    textPainter.paintBackground(
+                    textDelegate.paintBackground(
                         it.start, it.end, selectionColor, canvas
                     )
                 }
-                textPainter.paint(canvas)
+                textDelegate.paint(canvas)
             }
         }
         ComplexLayout(children) {
             measure { _, constraints ->
-                textPainter.layout(constraints)
-                layout(textPainter.width.px.round(), textPainter.height.px.round()) {}
+                textDelegate.layout(constraints)
+                layout(textDelegate.width.px.round(), textDelegate.height.px.round()) {}
             }
             minIntrinsicWidth { _, _ ->
                 // TODO(popam): discuss with the Text team about this
@@ -251,16 +251,16 @@
                 // textDelegate.minIntrinsicWidth.px.round()
             }
             minIntrinsicHeight { _, w ->
-                textPainter.layout(Constraints(0.ipx, w, 0.ipx, IntPx.Infinity))
-                textPainter.height.px.round()
+                textDelegate.layout(Constraints(0.ipx, w, 0.ipx, IntPx.Infinity))
+                textDelegate.height.px.round()
             }
             maxIntrinsicWidth { _, h ->
-                textPainter.layout(Constraints(0.ipx, IntPx.Infinity, 0.ipx, h))
-                textPainter.maxIntrinsicWidth.px.round()
+                textDelegate.layout(Constraints(0.ipx, IntPx.Infinity, 0.ipx, h))
+                textDelegate.maxIntrinsicWidth.px.round()
             }
             maxIntrinsicHeight { _, w ->
-                textPainter.layout(Constraints(0.ipx, w, 0.ipx, IntPx.Infinity))
-                textPainter.height.px.round()
+                textDelegate.layout(Constraints(0.ipx, w, 0.ipx, IntPx.Infinity))
+                textDelegate.height.px.round()
             }
         }
 
@@ -290,7 +290,7 @@
                             selectionCoordinates = Pair(startPx, endPx),
                             mode = mode,
                             onSelectionChange = { internalSelection.value = it },
-                            textDelegate = textPainter
+                            textDelegate = textDelegate
                         )
                         if (!textSelectionProcessor.isSelected) return null
 
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
index 9dfb3d3..cf28b6e 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
@@ -100,7 +100,7 @@
     val (visualText, offsetMap) = +memo(value, visualTransformation) {
         TextFieldDelegate.applyVisualFilter(value, visualTransformation)
     }
-    val textPainter = +memo(visualText, mergedStyle, density, resourceLoader) {
+    val textDelegate = +memo(visualText, mergedStyle, density, resourceLoader) {
         // TODO(nona): Add parameter for text direction, softwrap, etc.
         TextDelegate(
             text = visualText,
@@ -131,7 +131,7 @@
                 textInputService?.let { textInputService ->
                     TextFieldDelegate.notifyFocusedRect(
                         value,
-                        textPainter,
+                        textDelegate,
                         coords,
                         textInputService,
                         hasFocus.value,
@@ -153,7 +153,7 @@
         onRelease = {
             TextFieldDelegate.onRelease(
                 it,
-                textPainter,
+                textDelegate,
                 processor,
                 offsetMap,
                 onValueChange,
@@ -170,7 +170,7 @@
                         coords.value = it
                         TextFieldDelegate.notifyFocusedRect(
                             value,
-                            textPainter,
+                            textDelegate,
                             it,
                             textInputService,
                             hasFocus.value,
@@ -182,12 +182,12 @@
                     canvas,
                     value,
                     offsetMap,
-                    textPainter,
+                    textDelegate,
                     hasFocus.value,
                     editorStyle) }
             },
             measureBlock = { _, constraints ->
-                TextFieldDelegate.layout(textPainter, constraints).let {
+                TextFieldDelegate.layout(textDelegate, constraints).let {
                     layout(it.first, it.second) {}
                 }
             }
diff --git a/ui/ui-layout/api/1.0.0-alpha01.txt b/ui/ui-layout/api/1.0.0-alpha01.txt
index 76dd60f..6a517d6 100644
--- a/ui/ui-layout/api/1.0.0-alpha01.txt
+++ b/ui/ui-layout/api/1.0.0-alpha01.txt
@@ -142,23 +142,6 @@
     method public static void Padding(androidx.ui.core.Dp padding, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
-  public final class ScrollerKt {
-    ctor public ScrollerKt();
-    method public static void HorizontalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({ 
-    <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
-    method public static void VerticalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({ 
-    <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
-  }
-
-  public final class ScrollerPosition {
-    ctor public ScrollerPosition();
-    method public androidx.ui.core.Px getPosition();
-    method public void setPosition(androidx.ui.core.Px p);
-    property public final androidx.ui.core.Px position;
-  }
-
   public final class SpacerKt {
     ctor public SpacerKt();
     method public static void FixedSpacer(androidx.ui.core.Dp width, androidx.ui.core.Dp height);
diff --git a/ui/ui-layout/api/current.txt b/ui/ui-layout/api/current.txt
index 76dd60f..6a517d6 100644
--- a/ui/ui-layout/api/current.txt
+++ b/ui/ui-layout/api/current.txt
@@ -142,23 +142,6 @@
     method public static void Padding(androidx.ui.core.Dp padding, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
-  public final class ScrollerKt {
-    ctor public ScrollerKt();
-    method public static void HorizontalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({ 
-    <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
-    method public static void VerticalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({ 
-    <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
-  }
-
-  public final class ScrollerPosition {
-    ctor public ScrollerPosition();
-    method public androidx.ui.core.Px getPosition();
-    method public void setPosition(androidx.ui.core.Px p);
-    property public final androidx.ui.core.Px position;
-  }
-
   public final class SpacerKt {
     ctor public SpacerKt();
     method public static void FixedSpacer(androidx.ui.core.Dp width, androidx.ui.core.Dp height);
diff --git a/ui/ui-layout/api/restricted_1.0.0-alpha01.txt b/ui/ui-layout/api/restricted_1.0.0-alpha01.txt
index 76dd60f..6a517d6 100644
--- a/ui/ui-layout/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-layout/api/restricted_1.0.0-alpha01.txt
@@ -142,23 +142,6 @@
     method public static void Padding(androidx.ui.core.Dp padding, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
-  public final class ScrollerKt {
-    ctor public ScrollerKt();
-    method public static void HorizontalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({ 
-    <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
-    method public static void VerticalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({ 
-    <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
-  }
-
-  public final class ScrollerPosition {
-    ctor public ScrollerPosition();
-    method public androidx.ui.core.Px getPosition();
-    method public void setPosition(androidx.ui.core.Px p);
-    property public final androidx.ui.core.Px position;
-  }
-
   public final class SpacerKt {
     ctor public SpacerKt();
     method public static void FixedSpacer(androidx.ui.core.Dp width, androidx.ui.core.Dp height);
diff --git a/ui/ui-layout/api/restricted_current.txt b/ui/ui-layout/api/restricted_current.txt
index 76dd60f..6a517d6 100644
--- a/ui/ui-layout/api/restricted_current.txt
+++ b/ui/ui-layout/api/restricted_current.txt
@@ -142,23 +142,6 @@
     method public static void Padding(androidx.ui.core.Dp padding, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
-  public final class ScrollerKt {
-    ctor public ScrollerKt();
-    method public static void HorizontalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({ 
-    <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
-    method public static void VerticalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({ 
-    <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
-  }
-
-  public final class ScrollerPosition {
-    ctor public ScrollerPosition();
-    method public androidx.ui.core.Px getPosition();
-    method public void setPosition(androidx.ui.core.Px p);
-    property public final androidx.ui.core.Px position;
-  }
-
   public final class SpacerKt {
     ctor public SpacerKt();
     method public static void FixedSpacer(androidx.ui.core.Dp width, androidx.ui.core.Dp height);
diff --git a/ui/ui-layout/integration-tests/layout-demos/src/main/AndroidManifest.xml b/ui/ui-layout/integration-tests/layout-demos/src/main/AndroidManifest.xml
index 59cf792..0bbe95f 100644
--- a/ui/ui-layout/integration-tests/layout-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-layout/integration-tests/layout-demos/src/main/AndroidManifest.xml
@@ -37,15 +37,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".ScrollerActivity"
-            android:configChanges="orientation|screenSize"
-            android:label="Scroller">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="androidx.ui.demos.SAMPLE_CODE" />
-            </intent-filter>
-        </activity>
-
         <activity android:name=".TableActivity"
             android:configChanges="orientation|screenSize"
             android:label="Layout/Table">
diff --git a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ScrollerActivity.kt b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ScrollerActivity.kt
deleted file mode 100644
index e59ce03..0000000
--- a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ScrollerActivity.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.ui.layout.demos
-
-import android.app.Activity
-import android.os.Bundle
-import androidx.ui.core.Density
-import androidx.ui.core.Text
-import androidx.ui.core.dp
-import androidx.ui.core.withDensity
-import androidx.ui.layout.Column
-import androidx.ui.layout.Padding
-import androidx.ui.layout.VerticalScroller
-import androidx.compose.composer
-import androidx.ui.text.TextStyle
-import androidx.ui.core.setContent
-import androidx.ui.core.sp
-
-class ScrollerActivity : Activity() {
-    private val phrases = listOf(
-        "Easy As Pie",
-        "Wouldn't Harm a Fly",
-        "No-Brainer",
-        "Keep On Truckin'",
-        "An Arm and a Leg",
-        "Down To Earth",
-        "Under the Weather",
-        "Up In Arms",
-        "Cup Of Joe",
-        "Not the Sharpest Tool in the Shed",
-        "Ring Any Bells?",
-        "Son of a Gun",
-        "Hard Pill to Swallow",
-        "Close But No Cigar",
-        "Beating a Dead Horse",
-        "If You Can't Stand the Heat, Get Out of the Kitchen",
-        "Cut To The Chase",
-        "Heads Up",
-        "Goody Two-Shoes",
-        "Fish Out Of Water",
-        "Cry Over Spilt Milk",
-        "Elephant in the Room",
-        "There's No I in Team",
-        "Poke Fun At",
-        "Talk the Talk",
-        "Know the Ropes",
-        "Fool's Gold",
-        "It's Not Brain Surgery",
-        "Fight Fire With Fire",
-        "Go For Broke"
-    )
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        val density = Density(this)
-        withDensity(density) {
-            val style = TextStyle(fontSize = 30.sp)
-            setContent {
-                Padding(padding = 10.dp) {
-                    VerticalScroller {
-                        Column {
-                            phrases.forEach { phrase ->
-                                Text(text = phrase, style = style)
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
deleted file mode 100644
index 7686861..0000000
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.ui.layout
-
-import androidx.ui.core.coerceIn
-import androidx.ui.core.Constraints
-import androidx.ui.core.Direction
-import androidx.ui.core.IntPx
-import androidx.ui.core.Layout
-import androidx.ui.core.Px
-import androidx.ui.core.PxPosition
-import androidx.ui.core.gesture.TouchSlopDragGestureDetector
-import androidx.ui.core.gesture.DragObserver
-import androidx.ui.core.min
-import androidx.ui.core.px
-import androidx.ui.core.round
-import androidx.ui.core.toPx
-import androidx.ui.core.toRect
-import androidx.compose.Composable
-import androidx.compose.Model
-import androidx.compose.composer
-import androidx.compose.memo
-import androidx.compose.state
-import androidx.compose.unaryPlus
-import androidx.ui.core.Clip
-import androidx.ui.core.Density
-import androidx.ui.core.PxSize
-import androidx.ui.core.RepaintBoundary
-import androidx.ui.engine.geometry.Outline
-import androidx.ui.engine.geometry.Shape
-
-/**
- * Tracks the vertical drag gesture offset, allowing a range between `0.px` and [max].
- * When the offset changes, [offsetChange] is called with the new offset.
- */
-@Composable
-private fun DirectionalDragGestureDetector(
-    vertical: Boolean,
-    max: Px = Px.Infinity,
-    offsetChange: (Px) -> Unit,
-    children: @Composable() () -> Unit
-) {
-    val offset = +state { 0.px }
-    TouchSlopDragGestureDetector(
-        dragObserver = object : DragObserver {
-            override fun onDrag(dragDistance: PxPosition): PxPosition {
-                val draggedAmount = if (vertical) dragDistance.y else dragDistance.x
-                val dragPosition = -offset.value + draggedAmount
-                val targetPosition = dragPosition.coerceIn(-max, 0.px)
-                if (targetPosition != -offset.value) {
-                    offset.value = -targetPosition
-                    offsetChange(offset.value)
-                }
-                val consumed = draggedAmount + (targetPosition - dragPosition)
-                return if (vertical) PxPosition(0.px, consumed) else PxPosition(consumed, 0.px)
-            }
-        },
-        canDrag = { direction ->
-            when (direction) {
-                Direction.DOWN -> if (vertical) offset.value > 0.px else false
-                Direction.UP -> if (vertical) offset.value < max else false
-                Direction.RIGHT -> if (vertical) false else offset.value > 0.px
-                Direction.LEFT -> if (vertical) false else offset.value < max
-            }
-        }) { children() }
-}
-
-/**
- * This is the state of a [VerticalScroller] that allows the developer to change the scroll
- * position. [position] must be between `0` and `maxPosition` in `onScrollChanged`'s `maxPosition`
- * parameter.
- */
-@Model
-class ScrollerPosition {
-    /**
-     * The amount of scrolling, between `0` and `maxPosition` in `onScrollChanged`'s `maxPosition`
-     * parameter.
-     */
-    var position: Px = 0.px
-}
-
-/**
- * A container that composes all of its contents and lays it out, fitting the width of the child.
- * If the child's height is less than the [Constraints.maxHeight], the child's height is used,
- * or the [Constraints.maxHeight] otherwise. If the contents don't fit the height, the drag gesture
- * allows scrolling its content vertically. The contents of the VerticalScroller are clipped to
- * the VerticalScroller's bounds.
- */
-// TODO(mount): Add fling support
-@Composable
-fun VerticalScroller(
-    scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
-    onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
-        scrollerPosition.position = position
-    },
-    child: @Composable() () -> Unit
-) {
-    Scroller(
-        vertical = true,
-        scrollerPosition = scrollerPosition,
-        onScrollChanged = onScrollChanged,
-        child = child
-    )
-}
-
-/**
- * A container that composes all of its contents and lays it out, fitting the height of the child.
- * If the child's width is less than the [Constraints.maxWidth], the child's width is used,
- * or the [Constraints.maxWidth] otherwise. If the contents don't fit the width, the drag gesture
- * allows scrolling its content horizontally. The contents of the HorizontalScroller are clipped to
- * the HorizontalScroller's bounds.
- */
-// TODO(mount): Add fling support
-@Composable
-fun HorizontalScroller(
-    scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
-    onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
-        scrollerPosition.position = position
-    },
-    child: @Composable() () -> Unit
-) {
-    Scroller(
-        vertical = false,
-        scrollerPosition = scrollerPosition,
-        onScrollChanged = onScrollChanged,
-        child = child
-    )
-}
-
-@Composable
-private fun Scroller(
-    vertical: Boolean,
-    scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
-    onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
-        scrollerPosition.position = position
-    },
-    child: @Composable() () -> Unit
-) {
-    val maxPosition = +state { 0.px }
-    Layout(children = {
-        Clip(RectangleShape) {
-            DirectionalDragGestureDetector(
-                vertical = vertical,
-                max = maxPosition.value,
-                offsetChange = { newOffset -> onScrollChanged(newOffset, maxPosition.value) }) {
-                Container {
-                    RepaintBoundary {
-                        child()
-                    }
-                }
-            }
-        }
-    }) { measurables, constraints ->
-        val childConstraints = if (vertical) {
-            constraints.copy(maxHeight = IntPx.Infinity)
-        } else {
-            constraints.copy(maxWidth = IntPx.Infinity)
-        }
-        val childMeasurable = measurables.firstOrNull()
-        val placeable = childMeasurable?.measure(childConstraints)
-        val width: IntPx
-        val height: IntPx
-        if (placeable == null) {
-            width = constraints.minWidth
-            height = constraints.minHeight
-        } else {
-            width = min(placeable.width, constraints.maxWidth)
-            height = min(placeable.height, constraints.maxHeight)
-        }
-        layout(width, height) {
-            if (placeable != null) {
-                val childSize = if (vertical) {
-                    placeable.height.toPx()
-                } else {
-                    placeable.width.toPx()
-                }
-                val newMaxPosition = childSize - if (vertical) height.toPx() else width.toPx()
-                if (maxPosition.value != newMaxPosition) {
-                    maxPosition.value = newMaxPosition
-                    onScrollChanged(scrollerPosition.position, maxPosition.value)
-                }
-                val x: IntPx
-                val y: IntPx
-                if (vertical) {
-                    x = IntPx.Zero
-                    y = -scrollerPosition.position.round()
-                } else {
-                    x = -scrollerPosition.position.round()
-                    y = IntPx.Zero
-                }
-                placeable.place(x, y)
-            }
-        }
-    }
-}
-
-// TODO(andreykulikov): make RectangleShape from ui-foundation accessible to this class
-private val RectangleShape: Shape = object : Shape {
-    override fun createOutline(size: PxSize, density: Density) =
-        Outline.Rectangle(size.toRect())
-}
diff --git a/ui/ui-text/integration-tests/text-demos/build.gradle b/ui/ui-text/integration-tests/text-demos/build.gradle
index 389c24b..8254c7e2 100644
--- a/ui/ui-text/integration-tests/text-demos/build.gradle
+++ b/ui/ui-text/integration-tests/text-demos/build.gradle
@@ -20,6 +20,7 @@
     implementation project(":compose:compose-runtime")
     implementation project(":ui:ui-core")
     implementation project(':ui:ui-framework')
+    implementation project(':ui:ui-foundation')
     implementation project(':ui:ui-layout')
     implementation project(':ui:ui-text')
 }
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
index a775fd1..f590b27 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
@@ -27,7 +27,7 @@
 import androidx.ui.input.KeyboardType
 import androidx.ui.layout.Column
 import androidx.ui.layout.CrossAxisAlignment
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.VerticalScroller
 import androidx.ui.text.TextStyle
 
 val KEYBOARD_TYPES = listOf(
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
index e9af896..a37f2c0 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
@@ -38,7 +38,7 @@
 import androidx.ui.layout.Column
 import androidx.ui.layout.CrossAxisAlignment
 import androidx.ui.layout.Row
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.VerticalScroller
 import androidx.ui.text.ParagraphStyle
 import androidx.ui.painting.Shadow
 import androidx.compose.composer
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
index 05cfca9..2614a77 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
@@ -35,7 +35,7 @@
 import androidx.ui.input.KeyboardType
 import androidx.ui.layout.Column
 import androidx.ui.layout.CrossAxisAlignment
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.VerticalScroller
 import androidx.ui.text.AnnotatedString
 import androidx.ui.text.TextStyle
 import java.util.Locale
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextDelegateIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextDelegateIntegrationTest.kt
index 4ada2e9..2c0ffe6 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextDelegateIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextDelegateIntegrationTest.kt
@@ -56,9 +56,9 @@
 //    @Test
 //    fun preferredLineHeight_style_not_set() {
 //        val defaultTextStyle = TextStyle(fontFamily = fontFamily)
-//        val textPainter = TextDelegate(style = defaultTextStyle)
+//        val textDelegate = TextDelegate(style = defaultTextStyle)
 //
-//        val prefferedHeight = textPainter.preferredLineHeight
+//        val prefferedHeight = textDelegate.preferredLineHeight
 //
 //        assertThat(prefferedHeight).isEqualTo(14.0)
 //    }
@@ -72,16 +72,16 @@
             text = text,
             textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
         )
-        val textPainter = TextDelegate(
+        val textDelegate = TextDelegate(
             text = annotatedString,
             paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
             density = density,
             resourceLoader = resourceLoader
         )
 
-        textPainter.layout(Constraints())
+        textDelegate.layout(Constraints())
 
-        assertThat(textPainter.minIntrinsicWidth).isEqualTo(0.0f)
+        assertThat(textDelegate.minIntrinsicWidth).isEqualTo(0.0f)
     }
 
     @Test
@@ -100,16 +100,18 @@
                     )
                 )
             )
-            val textPainter = TextDelegate(
+            val textDelegate = TextDelegate(
                 text = annotatedString,
                 paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
                 density = density,
                 resourceLoader = resourceLoader
             )
 
-            textPainter.layout(Constraints())
+            textDelegate.layout(Constraints())
 
-            assertThat(textPainter.maxIntrinsicWidth).isEqualTo(fontSize.toPx().value * text.length)
+            assertThat(
+                textDelegate.maxIntrinsicWidth).isEqualTo(fontSize.toPx().value * text.length
+            )
         }
     }
 
@@ -129,16 +131,16 @@
                     )
                 )
             )
-            val textPainter = TextDelegate(
+            val textDelegate = TextDelegate(
                 text = annotatedString,
                 paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
                 density = density,
                 resourceLoader = resourceLoader
             )
 
-            textPainter.layout(Constraints(0.ipx, 200.ipx))
+            textDelegate.layout(Constraints(0.ipx, 200.ipx))
 
-            assertThat(textPainter.width).isEqualTo(fontSize.toPx().value * text.length)
+            assertThat(textDelegate.width).isEqualTo(fontSize.toPx().value * text.length)
         }
     }
 
@@ -151,16 +153,16 @@
             text = text,
             textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
         )
-        val textPainter = TextDelegate(
+        val textDelegate = TextDelegate(
             text = annotatedString,
             paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
             density = density,
             resourceLoader = resourceLoader
         )
 
-        textPainter.layout(Constraints(maxWidth = width))
+        textDelegate.layout(Constraints(maxWidth = width))
 
-        assertThat(textPainter.width).isEqualTo(width.value.toFloat())
+        assertThat(textDelegate.width).isEqualTo(width.value.toFloat())
     }
 
     @Test
@@ -179,31 +181,31 @@
                     )
                 )
             )
-            val textPainter = TextDelegate(
+            val textDelegate = TextDelegate(
                 text = annotatedString,
                 paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
                 density = density,
                 resourceLoader = resourceLoader
             )
 
-            textPainter.layout(Constraints())
+            textDelegate.layout(Constraints())
 
-            assertThat(textPainter.height).isEqualTo(fontSize.toPx().value)
+            assertThat(textDelegate.height).isEqualTo(fontSize.toPx().value)
         }
     }
 
     @Test
     fun layout_build_layoutResult() {
-        val textPainter = TextDelegate(
+        val textDelegate = TextDelegate(
             text = AnnotatedString(text = "Hello"),
             paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
             density = density,
             resourceLoader = resourceLoader
         )
 
-        textPainter.layout(Constraints(0.ipx, 20.ipx))
+        textDelegate.layout(Constraints(0.ipx, 20.ipx))
 
-        assertThat(textPainter.layoutResult).isNotNull()
+        assertThat(textDelegate.layoutResult).isNotNull()
     }
 
     @Test
@@ -219,15 +221,15 @@
                 )
             )
         )
-        val textPainter = TextDelegate(
+        val textDelegate = TextDelegate(
             text = annotatedString,
             paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
             density = density,
             resourceLoader = resourceLoader
         )
-        textPainter.layout(Constraints())
+        textDelegate.layout(Constraints())
 
-        val selection = textPainter.getOffsetForPosition(PxPosition.Origin)
+        val selection = textDelegate.getOffsetForPosition(PxPosition.Origin)
 
         assertThat(selection).isEqualTo(0)
     }
@@ -248,15 +250,15 @@
                     )
                 )
             )
-            val textPainter = TextDelegate(
+            val textDelegate = TextDelegate(
                 text = annotatedString,
                 paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
                 density = density,
                 resourceLoader = resourceLoader
             )
-            textPainter.layout(Constraints())
+            textDelegate.layout(Constraints())
 
-            val selection = textPainter.getOffsetForPosition(
+            val selection = textDelegate.getOffsetForPosition(
                 position = PxPosition((fontSize.toPx().value * characterIndex + 1).px, 0.px)
             )
 
@@ -272,16 +274,16 @@
             text = text,
             textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
         )
-        val textPainter = TextDelegate(
+        val textDelegate = TextDelegate(
             text = annotatedString,
             paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
             density = density,
             resourceLoader = resourceLoader
         )
 
-        textPainter.layout(Constraints())
+        textDelegate.layout(Constraints())
 
-        assertThat(textPainter.hasVisualOverflow).isFalse()
+        assertThat(textDelegate.hasVisualOverflow).isFalse()
     }
 
     @Test
@@ -295,7 +297,7 @@
             text = text,
             textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
         )
-        val textPainter = TextDelegate(
+        val textDelegate = TextDelegate(
             text = annotatedString,
             paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
             overflow = TextOverflow.Fade,
@@ -305,9 +307,9 @@
             resourceLoader = resourceLoader
         )
 
-        textPainter.layout(Constraints(maxWidth = 100.ipx))
+        textDelegate.layout(Constraints(maxWidth = 100.ipx))
 
-        assertThat(textPainter.hasVisualOverflow).isTrue()
+        assertThat(textDelegate.hasVisualOverflow).isTrue()
     }
 
     @Test
@@ -321,7 +323,7 @@
             text = text,
             textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
         )
-        val textPainter = TextDelegate(
+        val textDelegate = TextDelegate(
             text = annotatedString,
             paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
             overflow = TextOverflow.Fade,
@@ -330,9 +332,9 @@
             resourceLoader = resourceLoader
         )
 
-        textPainter.layout(Constraints(maxWidth = 100.ipx))
+        textDelegate.layout(Constraints(maxWidth = 100.ipx))
 
-        assertThat(textPainter.hasVisualOverflow).isTrue()
+        assertThat(textDelegate.hasVisualOverflow).isTrue()
     }
 
     @Test
@@ -353,17 +355,17 @@
                     )
                 )
             )
-            val textPainter = TextDelegate(
+            val textDelegate = TextDelegate(
                 text = annotatedString,
                 paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
                 density = density,
                 resourceLoader = resourceLoader
             )
-            textPainter.layout(Constraints(maxWidth = 120.ipx))
+            textDelegate.layout(Constraints(maxWidth = 120.ipx))
 
             val expectedBitmap = Bitmap.createBitmap(
-                ceil(textPainter.width).toInt(),
-                ceil(textPainter.height).toInt(),
+                ceil(textDelegate.width).toInt(),
+                ceil(textDelegate.height).toInt(),
                 Bitmap.Config.ARGB_8888
             )
             val expectedCanvas =
@@ -372,10 +374,10 @@
             val defaultSelectionColor = Color(0x6633B5E5)
             expectedPaint.color = defaultSelectionColor
 
-            val firstLineLeft = textPainter.layoutResult?.multiParagraph?.getLineLeft(0)
-            val secondLineLeft = textPainter.layoutResult?.multiParagraph?.getLineLeft(1)
-            val firstLineRight = textPainter.layoutResult?.multiParagraph?.getLineRight(0)
-            val secondLineRight = textPainter.layoutResult?.multiParagraph?.getLineRight(1)
+            val firstLineLeft = textDelegate.layoutResult?.multiParagraph?.getLineLeft(0)
+            val secondLineLeft = textDelegate.layoutResult?.multiParagraph?.getLineLeft(1)
+            val firstLineRight = textDelegate.layoutResult?.multiParagraph?.getLineRight(0)
+            val secondLineRight = textDelegate.layoutResult?.multiParagraph?.getLineRight(1)
             expectedCanvas.drawRect(
                 Rect(firstLineLeft!!, 0f, firstLineRight!!, fontSizeInPx),
                 expectedPaint
@@ -385,21 +387,21 @@
                     secondLineLeft!!,
                     fontSizeInPx,
                     secondLineRight!!,
-                    textPainter.layoutResult!!.multiParagraph.height
+                    textDelegate.layoutResult!!.multiParagraph.height
                 ),
                 expectedPaint
             )
 
             val actualBitmap = Bitmap.createBitmap(
-                ceil(textPainter.width).toInt(),
-                ceil(textPainter.height).toInt(),
+                ceil(textDelegate.width).toInt(),
+                ceil(textDelegate.height).toInt(),
                 Bitmap.Config.ARGB_8888
             )
             val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
 
             // Run.
             // Select all.
-            textPainter.paintBackground(
+            textDelegate.paintBackground(
                 start = 0,
                 end = text.length,
                 color = defaultSelectionColor,
@@ -431,17 +433,17 @@
                     )
                 )
             )
-            val textPainter = TextDelegate(
+            val textDelegate = TextDelegate(
                 text = annotatedString,
                 paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
                 density = density,
                 resourceLoader = resourceLoader
             )
-            textPainter.layout(Constraints())
+            textDelegate.layout(Constraints())
 
             val expectedBitmap = Bitmap.createBitmap(
-                ceil(textPainter.width).toInt(),
-                ceil(textPainter.height).toInt(),
+                ceil(textDelegate.width).toInt(),
+                ceil(textDelegate.height).toInt(),
                 Bitmap.Config.ARGB_8888
             )
             val expectedCanvas =
@@ -460,14 +462,14 @@
             )
 
             val actualBitmap = Bitmap.createBitmap(
-                ceil(textPainter.width).toInt(),
-                ceil(textPainter.height).toInt(),
+                ceil(textDelegate.width).toInt(),
+                ceil(textDelegate.height).toInt(),
                 Bitmap.Config.ARGB_8888
             )
             val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
 
             // Run.
-            textPainter.paintBackground(
+            textDelegate.paintBackground(
                 start = selectionStart,
                 end = selectionEnd,
                 color = defaultSelectionColor,
@@ -502,17 +504,17 @@
                     )
                 )
             )
-            val textPainter = TextDelegate(
+            val textDelegate = TextDelegate(
                 text = annotatedString,
                 paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
                 density = density,
                 resourceLoader = resourceLoader
             )
-            textPainter.layout(Constraints())
+            textDelegate.layout(Constraints())
 
             val expectedBitmap = Bitmap.createBitmap(
-                ceil(textPainter.width).toInt(),
-                ceil(textPainter.height).toInt(),
+                ceil(textDelegate.width).toInt(),
+                ceil(textDelegate.height).toInt(),
                 Bitmap.Config.ARGB_8888
             )
             val expectedCanvas =
@@ -543,14 +545,14 @@
             )
 
             val actualBitmap = Bitmap.createBitmap(
-                ceil(textPainter.width).toInt(),
-                ceil(textPainter.height).toInt(),
+                ceil(textDelegate.width).toInt(),
+                ceil(textDelegate.height).toInt(),
                 Bitmap.Config.ARGB_8888
             )
             val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
 
             // Run.
-            textPainter.paintBackground(
+            textDelegate.paintBackground(
                 start = selectionLTRStart,
                 end = textLTR.length + selectionRTLEnd,
                 color = defaultSelectionColor,
@@ -583,17 +585,17 @@
                 )
             )
             val selectionColor = Color(0x66AABB33)
-            val textPainter = TextDelegate(
+            val textDelegate = TextDelegate(
                 text = annotatedString,
                 paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
                 density = density,
                 resourceLoader = resourceLoader
             )
-            textPainter.layout(Constraints())
+            textDelegate.layout(Constraints())
 
             val expectedBitmap = Bitmap.createBitmap(
-                ceil(textPainter.width).toInt(),
-                ceil(textPainter.height).toInt(),
+                ceil(textDelegate.width).toInt(),
+                ceil(textDelegate.height).toInt(),
                 Bitmap.Config.ARGB_8888
             )
             val expectedCanvas =
@@ -611,14 +613,14 @@
             )
 
             val actualBitmap = Bitmap.createBitmap(
-                ceil(textPainter.width).toInt(),
-                ceil(textPainter.height).toInt(),
+                ceil(textDelegate.width).toInt(),
+                ceil(textDelegate.height).toInt(),
                 Bitmap.Config.ARGB_8888
             )
             val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
 
             // Run.
-            textPainter.paintBackground(
+            textDelegate.paintBackground(
                 start = selectionStart,
                 end = selectionEnd,
                 color = selectionColor,
diff --git a/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index f71c46d..485fc03 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -407,8 +407,8 @@
     /**
      * Feature for {@link #isFeatureSupported(String)}.
      * This feature covers
-     * {@link WebViewCompat#setForceDark(WebSettings, int)} and
-     * {@link WebViewCompat#getForceDark(WebSettings)}.
+     * {@link WebSettingsCompat#setForceDark(WebSettings, int)} and
+     * {@link WebSettingsCompat#getForceDark(WebSettings)}.
      *
      * TODO(amalova): unhide
      * @hide