Merge "Add test for seek to end of media item" into androidx-master-dev
diff --git a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index 427c11a..7218e30 100644
--- a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -28,6 +28,7 @@
 import org.gradle.api.publish.PublishingExtension
 import org.gradle.api.publish.maven.MavenPom
 import org.gradle.api.publish.maven.MavenPublication
+import org.gradle.api.publish.tasks.GenerateModuleMetadata
 import org.gradle.kotlin.dsl.configure
 import org.gradle.kotlin.dsl.create
 import java.io.File
@@ -49,6 +50,9 @@
     if (extension.publish.shouldPublish() && component.isAndroidOrJavaReleaseComponent()) {
         val androidxGroup = validateCoordinatesAndGetGroup(extension)
         group = androidxGroup
+        tasks.withType(GenerateModuleMetadata::class.java) {
+            it.enabled = false
+        }
         configure<PublishingExtension> {
             repositories {
                 it.maven { repo ->
diff --git a/buildSrc/src/main/kotlin/androidx/build/TaskUpToDateValidator.kt b/buildSrc/src/main/kotlin/androidx/build/TaskUpToDateValidator.kt
index 30e3241..3624a89 100644
--- a/buildSrc/src/main/kotlin/androidx/build/TaskUpToDateValidator.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/TaskUpToDateValidator.kt
@@ -78,6 +78,7 @@
     "partiallyDejetifyArchive",
     "postInstrumentCode",
     "publishBenchmarkPluginMarkerMavenPublicationToMavenRepository",
+    "publishKotlinMultiplatformPublicationToMavenRepository",
     "publishMavenPublicationToMavenRepository",
     "publishMetadataPublicationToMavenRepository",
     "publishPluginMavenPublicationToMavenRepository",
diff --git a/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt b/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt
index ada5665..4adf272 100644
--- a/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt
@@ -23,6 +23,7 @@
 import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.javadoc.Javadoc
 import org.gradle.external.javadoc.CoreJavadocOptions
+import org.gradle.external.javadoc.StandardJavadocDocletOptions
 import java.io.File
 
 // external/doclava/src/com/google/doclava/Errors.java
@@ -145,7 +146,7 @@
      * "Configures" this DoclavaTask with parameters that might not be at their final values
      * until this task is run.
      */
-    private fun configureDoclava() = (options as CoreJavadocOptions).apply {
+    private fun configureDoclava() = (options as StandardJavadocDocletOptions).apply {
 
         docletpath = this@DoclavaTask.docletpath
 
@@ -181,6 +182,9 @@
         }
         // Always treat this as an Android docs task.
         addBooleanOption("android", true)
+
+        // Doclava does not understand -notimestamp option that is default since Gradle 6.0
+        isNoTimestamp = false
     }
 
     fun coreJavadocOptions(configure: CoreJavadocOptions.() -> Unit) =
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
index de986d5..a5884bf 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
@@ -27,6 +27,8 @@
 import org.gradle.api.Project
 import org.gradle.api.artifacts.ResolveException
 import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputFiles
 import org.gradle.api.tasks.TaskAction
 import org.gradle.api.tasks.TaskCollection
 import org.gradle.api.tasks.TaskContainer
@@ -215,14 +217,22 @@
 // TODO(b/143243490): can this be made to run more quickly, cleanly and clearly via some other
 // approach, such as maybe with an ArtifactTransform?
 open class LocateJarsTask : DefaultTask() {
+    init {
+        // This task does not correctly model inputs and outputs.
+        // Mark it to be never up to date
+        outputs.upToDateWhen { false }
+    }
+
     // dependencies to search for .jar files
+    @get:Input
     val inputDependencies = mutableListOf<String>()
 
     // .jar files found in any dependencies
+    @get:OutputFiles
     val outputJars = mutableListOf<File>()
 
     @TaskAction
-    fun Extract() {
+    fun extract() {
         // setup
         val inputDependencies = checkNotNull(inputDependencies) { "inputDependencies not set" }
         val project = this.project
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
index 921e399..6aa1992 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
@@ -73,10 +73,12 @@
      * {@link FocusMeteringAction} are executed in a row, only the latest one will work and
      * other actions will be cancelled.
      *
-     * <p>If the {@link FocusMeteringAction} specifies more AF/AE/AWB regions than what is
-     * supported on current device, only the first region and then in order up to the number of
-     * regions supported by the device will be enabled. If it turns out no added regions can be
-     * supported on the device, the returned {@link ListenableFuture} in
+     * <p>If the {@link FocusMeteringAction} specifies more AF/AE/AWB points than what is
+     * supported on current device, only the first point and then in order up to the number of
+     * points supported by the device will be enabled.
+     *
+     * <p>If none of the points with either AF/AE/AWB can be supported on the device,
+     * the returned {@link ListenableFuture} in
      * {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} will fail immediately.
      *
      * @see FocusMeteringAction
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
index 95502661..697ae89 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
@@ -175,11 +175,19 @@
          * Adds another {@link MeteringPoint} with default mode {@link MeteringMode#AF} |
          * {@link MeteringMode#AE} | {@link MeteringMode#AWB}.
          *
+         * <p>The points added here will be appended in order after the point set in
+         * {@link FocusMeteringAction.Builder#from(MeteringPoint)} or
+         * {@link FocusMeteringAction.Builder#from(MeteringPoint, int)}.
+         *
          * <p>If more points are added than what current device supports for AF/AE/AWB, only the
-         * first region and then in order up to the number of regions supported by the device
-         * will be enabled. If it turns out no added points can be supported on the device, the
-         * returned {@link ListenableFuture} in
-         * {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} will fail immediately.
+         * first point and then in order up to the number of points supported on the device
+         * will be enabled.
+         *
+         * <p>If none of the points is supported on the device, this
+         * {@link FocusMeteringAction} will cause
+         * {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} to fail.
+         *
+         * @see CameraControl#startFocusAndMetering(FocusMeteringAction)
          */
         @NonNull
         public Builder addPoint(@NonNull MeteringPoint point) {
@@ -189,14 +197,19 @@
         /**
          * Adds another {@link MeteringPoint} with specified {@link MeteringMode}.
          *
-         * <p>If more points are added than what current device supports for AF/AE/AWB, only the
-         * first region and then in order up to the number of regions supported by the device
-         * will be enabled. If it turns out no added points can be supported on the device, the
-         * returned {@link ListenableFuture} in
-         * {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} will fail immediately.
+         * <p>The points added here will be appended in order after the point set in
+         * {@link FocusMeteringAction.Builder#from(MeteringPoint)} or
+         * {@link FocusMeteringAction.Builder#from(MeteringPoint, int)}.
          *
-         * @param mode Must be a valid {@link MeteringMode}, otherwise an
-         *             {@link IllegalArgumentException} is thrown.
+         * <p>If more points are added than what current device supports for AF/AE/AWB, only the
+         * first point and then in order up to the number of points supported on the device
+         * will be enabled.
+         *
+         * <p>If none of the points is supported on the device, this
+         * {@link FocusMeteringAction} will cause
+         * {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} to fail.
+         *
+         * @see CameraControl#startFocusAndMetering(FocusMeteringAction)
          */
         @NonNull
         public Builder addPoint(@NonNull MeteringPoint point, @MeteringMode int mode) {
diff --git a/development/apilint.py b/development/apilint.py
index ad3d8d5..e523d26 100755
--- a/development/apilint.py
+++ b/development/apilint.py
@@ -19,19 +19,28 @@
 """Script that will remind developers to run updateApi."""
 
 import argparse
+import os.path
 import sys
 
 
-WARNING_COLOR = '\033[93m'
+
+WARNING_COLOR = '\033[33m'
 END_COLOR = '\033[0m'
 
-WARNING = """
+WARNING_NO_API_FILES = """
 {}**********************************************************************
 You changed library classes, but you have no current.txt changes.
 Did you forget to run ./gradlew updateApi?
 **********************************************************************{}
 """.format(WARNING_COLOR, END_COLOR)
 
+WARNING_OLD_API_FILES = """
+{}**********************************************************************
+Your current.txt is older than your current changes in library classes.
+Did you forget to re-run ./gradlew updateApi?
+**********************************************************************{}
+""".format(WARNING_COLOR, END_COLOR)
+
 
 def main(args=None):
   parser = argparse.ArgumentParser()
@@ -40,16 +49,24 @@
   args = parser.parse_args()
   api_files = [f for f in args.file
                if f.endswith('.txt') and '/api/' in f]
-  if len(api_files) > 0:
+  source_files = [f for f in args.file
+               if (not "buildSrc/" in f and
+                  "/src/main/" in f or
+                  "/src/commonMain/" in f or
+                  "/src/androidMain/" in f)]
+  if len(source_files) == 0:
     sys.exit(0)
 
-  for f in args.file:
-    if (not "buildSrc/" in f and
-        "/src/main/" in f or
-        "/src/commonMain/" in f or
-        "/src/androidMain/" in f):
-      print(WARNING)
-      sys.exit(77) # 77 is a warning code in repohooks
+  if len(api_files) == 0:
+    print(WARNING_NO_API_FILES)
+    sys.exit(77) # 77 is a warning code in repohooks
+
+  last_source_timestamp = max([os.path.getmtime(f) for f in source_files])
+  last_api_timestamp = max([os.path.getmtime(f) for f in api_files])
+
+  if last_source_timestamp > last_api_timestamp:
+    print(WARNING_OLD_API_FILES)
+    sys.exit(77) # 77 is a warning code in repohooks
   sys.exit(0)
 
 if __name__ == '__main__':
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index cf3bb85..9817e9f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -5,4 +5,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-5.6.2-bin.zip
+distributionUrl=../../../../tools/external/gradle/gradle-6.0-bin.zip
diff --git a/lifecycle/integration-tests/incrementality/build.gradle b/lifecycle/integration-tests/incrementality/build.gradle
index 8207fb4..f036777 100644
--- a/lifecycle/integration-tests/incrementality/build.gradle
+++ b/lifecycle/integration-tests/incrementality/build.gradle
@@ -66,4 +66,5 @@
 // lifecycle-common and annotation are the dependencies of lifecycle-compiler
 tasks.findByPath("test").dependsOn(tasks.findByPath(":lifecycle:lifecycle-compiler:publish"),
         tasks.findByPath(":lifecycle:lifecycle-common:publish"),
+        tasks.findByPath(":lifecycle:lifecycle-runtime:publish"),
         tasks.findByPath(":annotation:annotation:publish"))
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt
index b2dc7b3..7bdc5b8 100644
--- a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt
+++ b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt
@@ -17,13 +17,13 @@
 package androidx.lifecycle
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
+import androidx.test.filters.MediumTest
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@SmallTest
+@MediumTest
 @RunWith(AndroidJUnit4::class)
 class LaunchWhenTest {
     private val expectations = Expectations()
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java
index c648c91..f074256 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java
@@ -370,7 +370,7 @@
                 withAspectRatio(videoSizeFor1stItem.getWidth(), videoSizeFor1stItem.getHeight())));
 
         // seekTo instead of skipToNextItem (b/144876689)
-        playerWrapper.seekTo(Long.MAX_VALUE);
+        playerWrapper.seekTo(playerWrapper.getDurationMs());
         assertTrue(latchFor2ndItem.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
         onView(instanceOf(VideoSurfaceView.class)).check(matches(
                 withAspectRatio(videoSizeFor2ndItem.getWidth(), videoSizeFor2ndItem.getHeight())));
diff --git a/ui/gradle/wrapper/gradle-wrapper.properties b/ui/gradle/wrapper/gradle-wrapper.properties
index c62ad8a..ec9defd 100644
--- a/ui/gradle/wrapper/gradle-wrapper.properties
+++ b/ui/gradle/wrapper/gradle-wrapper.properties
@@ -5,4 +5,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=../../../../../tools/external/gradle/gradle-5.6.2-bin.zip
+distributionUrl=../../../../../tools/external/gradle/gradle-6.0-bin.zip
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RadioGroupBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RadioGroupBenchmark.kt
new file mode 100644
index 0000000..36ed034
--- /dev/null
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RadioGroupBenchmark.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.benchmark.test
+
+import androidx.compose.Composable
+import androidx.compose.Model
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.ui.benchmark.ComposeBenchmarkRule
+import androidx.ui.benchmark.benchmarkFirstCompose
+import androidx.ui.benchmark.benchmarkFirstDraw
+import androidx.ui.benchmark.benchmarkFirstLayout
+import androidx.ui.benchmark.benchmarkFirstMeasure
+import androidx.ui.benchmark.toggleStateBenchmarkDraw
+import androidx.ui.benchmark.toggleStateBenchmarkLayout
+import androidx.ui.benchmark.toggleStateBenchmarkMeasure
+import androidx.ui.benchmark.toggleStateBenchmarkRecompose
+import androidx.ui.layout.Column
+import androidx.ui.material.MaterialTheme
+import androidx.ui.material.RadioGroup
+import androidx.ui.test.ComposeTestCase
+import androidx.ui.test.ToggleableTestCase
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Benchmark for [RadioGroup].
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class RadioGroupBenchmark {
+
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val radioCaseFactory = { RadioGroupTestCase() }
+
+    @Test
+    fun first_compose() {
+        benchmarkRule.benchmarkFirstCompose(radioCaseFactory)
+    }
+
+    @Test
+    fun first_measure() {
+        benchmarkRule.benchmarkFirstMeasure(radioCaseFactory)
+    }
+
+    @Test
+    fun first_layout() {
+        benchmarkRule.benchmarkFirstLayout(radioCaseFactory)
+    }
+
+    @Test
+    fun first_draw() {
+        benchmarkRule.benchmarkFirstDraw(radioCaseFactory)
+    }
+
+    @Test
+    fun toggleRadio_recompose() {
+        benchmarkRule.toggleStateBenchmarkRecompose(radioCaseFactory)
+    }
+
+    @Test
+    fun toggleRadio_measure() {
+        benchmarkRule.toggleStateBenchmarkMeasure(radioCaseFactory)
+    }
+
+    @Test
+    fun toggleRadio_layout() {
+        benchmarkRule.toggleStateBenchmarkLayout(radioCaseFactory)
+    }
+
+    @Test
+    fun toggleRadio_draw() {
+        benchmarkRule.toggleStateBenchmarkDraw(radioCaseFactory)
+    }
+}
+
+@Model
+internal class RadioGroupSelectedState<T>(var selected: T)
+
+internal class RadioGroupTestCase : ComposeTestCase, ToggleableTestCase {
+
+    private val radiosCount = 10
+    private val options = (0 until radiosCount).toList()
+    private val select = RadioGroupSelectedState(0)
+
+    override fun toggleState() {
+        select.selected = (select.selected + 1) % radiosCount
+    }
+
+    @Composable
+    override fun emitContent() {
+        MaterialTheme {
+            RadioGroup {
+                Column {
+                    options.forEach { item ->
+                        RadioGroupTextItem(
+                            text = item.toString(),
+                            selected = (select.selected == item),
+                            onSelect = { select.selected = item })
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/core/WithConstraintsBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/core/WithConstraintsBenchmark.kt
index af313d2..c84a1c9 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/core/WithConstraintsBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/core/WithConstraintsBenchmark.kt
@@ -26,6 +26,7 @@
 import androidx.ui.benchmark.toggleStateBenchmarkMeasureLayout
 import androidx.ui.core.Placeable.PlacementScope.place
 import androidx.ui.layout.Container
+import androidx.ui.layout.Size
 import androidx.ui.layout.Spacer
 import androidx.ui.test.ComposeTestCase
 import androidx.ui.test.ToggleableTestCase
@@ -66,7 +67,7 @@
         val size = +state { 200.dp }
         this.state = size
         Container(width = 300.dp, height = 300.dp) {
-            Spacer(androidx.ui.layout.Size(width = size.value, height = size.value))
+            Spacer(Size(width = size.value, height = size.value))
         }
     }
 
@@ -85,7 +86,7 @@
         this.state = size
         WithConstraints {
             Container(width = 300.dp, height = 300.dp) {
-                Spacer(androidx.ui.layout.Size(width = size.value, height = size.value))
+                Spacer(Size(width = size.value, height = size.value))
             }
         }
     }
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt
index 907e572..8745eee 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt
@@ -24,6 +24,7 @@
 import androidx.ui.graphics.Color
 import androidx.ui.material.ColorPalette
 import androidx.ui.material.MaterialTheme
+import androidx.ui.material.lightColorPalette
 import androidx.ui.test.ComposeTestCase
 import androidx.ui.test.ToggleableTestCase
 
@@ -65,7 +66,7 @@
  */
 class ObservableColorPaletteTestCase : ColorPaletteTestCase() {
     override fun createPalette(primary: Color): ColorPalette {
-        return ColorPalette(primary = primary)
+        return lightColorPalette(primary = primary)
     }
 }
 
@@ -89,6 +90,7 @@
         override val onBackground = Color.Black
         override val onSurface = Color.Black
         override val onError = Color.Black
+        override val isLight = true
     }
 }
 
diff --git a/ui/ui-core/api/0.1.0-dev03.txt b/ui/ui-core/api/0.1.0-dev03.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/0.1.0-dev03.txt
+++ b/ui/ui-core/api/0.1.0-dev03.txt
@@ -5,6 +5,7 @@
     ctor public MathHelpersKt();
     method public static float lerp(float start, float stop, float fraction);
     method public static int lerp(int start, int stop, float fraction);
+    method public static long lerp(long start, long stop, float fraction);
     method public static String toHexString(int);
     method public static String toStringAsFixed(float, int digits);
   }
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -5,6 +5,7 @@
     ctor public MathHelpersKt();
     method public static float lerp(float start, float stop, float fraction);
     method public static int lerp(int start, int stop, float fraction);
+    method public static long lerp(long start, long stop, float fraction);
     method public static String toHexString(int);
     method public static String toStringAsFixed(float, int digits);
   }
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt
@@ -5,6 +5,7 @@
     ctor public MathHelpersKt();
     method public static float lerp(float start, float stop, float fraction);
     method public static int lerp(int start, int stop, float fraction);
+    method public static long lerp(long start, long stop, float fraction);
     method public static String toHexString(int);
     method public static String toStringAsFixed(float, int digits);
   }
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -5,6 +5,7 @@
     ctor public MathHelpersKt();
     method public static float lerp(float start, float stop, float fraction);
     method public static int lerp(int start, int stop, float fraction);
+    method public static long lerp(long start, long stop, float fraction);
     method public static String toHexString(int);
     method public static String toStringAsFixed(float, int digits);
   }
diff --git a/ui/ui-core/api/restricted_0.1.0-dev03.txt b/ui/ui-core/api/restricted_0.1.0-dev03.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev03.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev03.txt
@@ -5,6 +5,7 @@
     ctor public MathHelpersKt();
     method public static float lerp(float start, float stop, float fraction);
     method public static int lerp(int start, int stop, float fraction);
+    method public static long lerp(long start, long stop, float fraction);
     method public static String toHexString(int);
     method public static String toStringAsFixed(float, int digits);
   }
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -5,6 +5,7 @@
     ctor public MathHelpersKt();
     method public static float lerp(float start, float stop, float fraction);
     method public static int lerp(int start, int stop, float fraction);
+    method public static long lerp(long start, long stop, float fraction);
     method public static String toHexString(int);
     method public static String toStringAsFixed(float, int digits);
   }
diff --git a/ui/ui-core/src/main/java/androidx/ui/MathHelpers.kt b/ui/ui-core/src/main/java/androidx/ui/MathHelpers.kt
index 7b233d5..e707f3d 100644
--- a/ui/ui-core/src/main/java/androidx/ui/MathHelpers.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/MathHelpers.kt
@@ -16,19 +16,27 @@
 package androidx.ui
 
 import kotlin.math.roundToInt
+import kotlin.math.roundToLong
 
 /**
  * Linearly interpolate between [start] and [stop] with [fraction] fraction between them.
  */
 fun lerp(start: Float, stop: Float, fraction: Float): Float {
-    return start + (stop - start) * fraction
+    return (1 - fraction) * start + fraction * stop
 }
 
 /**
  * Linearly interpolate between [start] and [stop] with [fraction] fraction between them.
  */
 fun lerp(start: Int, stop: Int, fraction: Float): Int {
-    return start + ((stop - start) * fraction).roundToInt()
+    return start + ((stop - start) * fraction.toDouble()).roundToInt()
+}
+
+/**
+ * Linearly interpolate between [start] and [stop] with [fraction] fraction between them.
+ */
+fun lerp(start: Long, stop: Long, fraction: Float): Long {
+    return start + ((stop - start) * fraction.toDouble()).roundToLong()
 }
 
 fun Float.toStringAsFixed(digits: Int) = String.format("%.${digits}f", this)
diff --git a/ui/ui-core/src/test/java/androidx/ui/MathHelpersTest.kt b/ui/ui-core/src/test/java/androidx/ui/MathHelpersTest.kt
new file mode 100644
index 0000000..598938d
--- /dev/null
+++ b/ui/ui-core/src/test/java/androidx/ui/MathHelpersTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MathHelpersTest {
+
+    // `f = 16777216f` is the first value where `f + 1 == f` due to float imprecision, so that's
+    // where testing floating point errors becomes interesting
+    val testStart = 16777216L
+    val testEnd = testStart + 1000
+
+    @Test
+    fun testLerpLargeFloats() {
+        val from = 1f
+        for (x in testStart until testEnd) {
+            val to = x.toFloat()
+            assertThat(lerp(from, to, 0f)).isEqualTo(from)
+            assertThat(lerp(from, to, 1f)).isEqualTo(to)
+        }
+    }
+
+    @Test
+    fun testLerpLargeInts() {
+        val from = 1
+        for (x in testStart until testEnd) {
+            val to = x.toInt()
+            assertThat(lerp(from, to, 0f)).isEqualTo(from)
+            assertThat(lerp(from, to, 1f)).isEqualTo(to)
+        }
+    }
+
+    @Test
+    fun testLerpLargeLongs() {
+        val from = 1L
+        for (x in testStart until testEnd) {
+            val to = x.toLong()
+            assertThat(lerp(from, to, 0f)).isEqualTo(from)
+            assertThat(lerp(from, to, 1f)).isEqualTo(to)
+        }
+    }
+
+    @Test
+    fun testLerpSimpleFloats() {
+        val from = 0f
+        for (multiplier in 1..1000) {
+            val to = (4 * multiplier).toFloat()
+            assertThat(lerp(from, to, 0.00f)).isEqualTo((0 * multiplier).toFloat())
+            assertThat(lerp(from, to, 0.25f)).isEqualTo((1 * multiplier).toFloat())
+            assertThat(lerp(from, to, 0.50f)).isEqualTo((2 * multiplier).toFloat())
+            assertThat(lerp(from, to, 0.75f)).isEqualTo((3 * multiplier).toFloat())
+            assertThat(lerp(from, to, 1.00f)).isEqualTo((4 * multiplier).toFloat())
+        }
+    }
+
+    @Test
+    fun testLerpSimpleInts() {
+        val from = 0
+        for (multiplier in 1..1000) {
+            val to = (4 * multiplier).toInt()
+            assertThat(lerp(from, to, 0.00f)).isEqualTo((0 * multiplier).toInt())
+            assertThat(lerp(from, to, 0.25f)).isEqualTo((1 * multiplier).toInt())
+            assertThat(lerp(from, to, 0.50f)).isEqualTo((2 * multiplier).toInt())
+            assertThat(lerp(from, to, 0.75f)).isEqualTo((3 * multiplier).toInt())
+            assertThat(lerp(from, to, 1.00f)).isEqualTo((4 * multiplier).toInt())
+        }
+    }
+
+    @Test
+    fun testLerpSimpleLongs() {
+        val from = 0L
+        for (multiplier in 1..1000) {
+            val to = (4 * multiplier).toLong()
+            assertThat(lerp(from, to, 0.00f)).isEqualTo((0 * multiplier).toLong())
+            assertThat(lerp(from, to, 0.25f)).isEqualTo((1 * multiplier).toLong())
+            assertThat(lerp(from, to, 0.50f)).isEqualTo((2 * multiplier).toLong())
+            assertThat(lerp(from, to, 0.75f)).isEqualTo((3 * multiplier).toLong())
+            assertThat(lerp(from, to, 1.00f)).isEqualTo((4 * multiplier).toLong())
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
index 7f90d37..50e6c3a 100644
--- a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
@@ -16,24 +16,33 @@
 package androidx.ui.foundation
 
 import android.graphics.Bitmap
-import android.os.Build
 import android.os.Handler
+import android.os.Looper
 import android.view.PixelCopy
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
+import androidx.annotation.RequiresApi
 import androidx.compose.Composable
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import androidx.ui.core.Alignment
 import androidx.ui.core.AndroidComposeView
+import androidx.ui.core.Dp
 import androidx.ui.core.Draw
 import androidx.ui.core.IntPx
+import androidx.ui.core.Px
+import androidx.ui.core.TestTag
+import androidx.ui.core.Text
+import androidx.ui.core.dp
 import androidx.ui.core.ipx
 import androidx.ui.core.px
 import androidx.ui.core.setContent
+import androidx.ui.core.toPx
 import androidx.ui.core.toRect
 import androidx.ui.core.withDensity
+import androidx.ui.foundation.shape.DrawShape
+import androidx.ui.foundation.shape.RectangleShape
 import androidx.ui.graphics.Color
 import androidx.ui.graphics.Paint
 import androidx.ui.graphics.PaintingStyle
@@ -43,18 +52,23 @@
 import androidx.ui.layout.ConstrainedBox
 import androidx.ui.layout.Container
 import androidx.ui.layout.DpConstraints
-import androidx.ui.core.Text
-import androidx.ui.core.dp
-import androidx.ui.core.sp
-import androidx.ui.layout.Padding
 import androidx.ui.layout.Row
+import androidx.ui.semantics.Semantics
+import androidx.ui.test.GestureScope
+import androidx.ui.test.SemanticsNodeInteraction
 import androidx.ui.test.android.AndroidComposeTestRule
 import androidx.ui.test.assertIsDisplayed
 import androidx.ui.test.assertIsNotDisplayed
 import androidx.ui.test.createComposeRule
+import androidx.ui.test.doGesture
 import androidx.ui.test.doScrollTo
+import androidx.ui.test.findByTag
 import androidx.ui.test.findByText
-import androidx.ui.text.TextStyle
+import androidx.ui.test.sendSwipeDown
+import androidx.ui.test.sendSwipeLeft
+import androidx.ui.test.sendSwipeRight
+import androidx.ui.test.sendSwipeUp
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -74,10 +88,14 @@
 
     // TODO(malkov/pavlis) : some tests here require activity access as we need
     // to take screen's bitmap, abstract it better
-    val activity
+    private val activity
         get() = (composeTestRule as AndroidComposeTestRule).activityTestRule.activity
 
-    val colors = listOf(
+    private val defaultCrossAxisSize = 45.ipx
+    private val defaultMainAxisSize = 40.ipx
+    private val defaultCellSize = 5.ipx
+
+    private val colors = listOf(
         Color(red = 0xFF, green = 0, blue = 0, alpha = 0xFF),
         Color(red = 0xFF, green = 0xA5, blue = 0, alpha = 0xFF),
         Color(red = 0xFF, green = 0xFF, blue = 0, alpha = 0xFF),
@@ -88,8 +106,8 @@
         Color(red = 0xA5, green = 0, blue = 0xFF, alpha = 0xFF)
     )
 
-    var drawLatch = CountDownLatch(1)
-    lateinit var handler: Handler
+    private var drawLatch = CountDownLatch(1)
+    private lateinit var handler: Handler
 
     @Before
     fun setupDrawLatch() {
@@ -99,15 +117,16 @@
         }
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun verticalScroller_SmallContent() {
-        composeVerticalScroller()
+        val height = 40.ipx
 
-        validateVerticalScroller(0, 40)
+        composeVerticalScroller(height = height)
+
+        validateVerticalScroller(height = height)
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun verticalScroller_SmallContent_Unscrollable() {
         val scrollerPosition = ScrollerPosition()
@@ -115,9 +134,8 @@
         // latch to wait for a new max to come on layout
         val newMaxLatch = CountDownLatch(1)
 
-        composeVerticalScroller(
-            scrollerPosition
-        )
+        composeVerticalScroller(scrollerPosition)
+
         val onGlobalLayout = object : ViewTreeObserver.OnGlobalLayoutListener {
             override fun onGlobalLayout() {
                 newMaxLatch.countDown()
@@ -133,22 +151,26 @@
         }
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun verticalScroller_LargeContent_NoScroll() {
-        composeVerticalScroller(height = 30.ipx)
+        val height = 30.ipx
 
-        validateVerticalScroller(0, 30)
+        composeVerticalScroller(height = height)
+
+        validateVerticalScroller(height = height)
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun verticalScroller_LargeContent_ScrollToEnd() {
         val scrollerPosition = ScrollerPosition()
+        val height = 30.ipx
+        val scrollDistance = 10.ipx
 
-        composeVerticalScroller(scrollerPosition, height = 30.ipx)
+        composeVerticalScroller(scrollerPosition, height = height)
 
-        validateVerticalScroller(0, 30)
+        validateVerticalScroller(height = height)
 
         // The 'draw' method will no longer be called because only the position
         // changes during scrolling. Therefore, we should just wait until the draw stage
@@ -161,40 +183,47 @@
         }
         composeTestRule.runOnUiThread {
             activity.window.decorView.viewTreeObserver.addOnDrawListener(onDrawListener)
-            assertEquals(10.px, scrollerPosition.maxPosition)
-            scrollerPosition.scrollTo(10.px)
+            assertEquals(scrollDistance.toPx(), scrollerPosition.maxPosition)
+            scrollerPosition.scrollTo(scrollDistance.toPx())
         }
         assertTrue(latch.await(1, TimeUnit.SECONDS))
         composeTestRule.runOnUiThread {
             activity.window.decorView.viewTreeObserver.removeOnDrawListener(onDrawListener)
         }
-        validateVerticalScroller(10, 30)
+        validateVerticalScroller(offset = scrollDistance, height = height)
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun horizontalScroller_SmallContent() {
-        composeHorizontalScroller()
+        val width = 40.ipx
 
-        validateHorizontalScroller(0, 40)
+        composeHorizontalScroller(width = width)
+
+        validateHorizontalScroller(width = width)
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun horizontalScroller_LargeContent_NoScroll() {
-        composeHorizontalScroller(width = 30.ipx)
+        val width = 30.ipx
 
-        validateHorizontalScroller(0, 30)
+        composeHorizontalScroller(width = width)
+
+        validateHorizontalScroller(width = width)
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun horizontalScroller_LargeContent_ScrollToEnd() {
+        val width = 30.ipx
+        val scrollDistance = 10.ipx
+
         val scrollerPosition = ScrollerPosition()
 
-        composeHorizontalScroller(scrollerPosition, width = 30.ipx)
+        composeHorizontalScroller(scrollerPosition, width = width)
 
-        validateHorizontalScroller(0, 30)
+        validateHorizontalScroller(width = width)
 
         // The 'draw' method will no longer be called because only the position
         // changes during scrolling. Therefore, we should just wait until the draw stage
@@ -207,14 +236,14 @@
         }
         composeTestRule.runOnUiThread {
             activity.window.decorView.viewTreeObserver.addOnDrawListener(onDrawListener)
-            assertEquals(10.px, scrollerPosition.maxPosition)
-            scrollerPosition.scrollTo(10.px)
+            assertEquals(scrollDistance.toPx(), scrollerPosition.maxPosition)
+            scrollerPosition.scrollTo(scrollDistance.toPx())
         }
         assertTrue(latch.await(1, TimeUnit.SECONDS))
         composeTestRule.runOnUiThread {
             activity.window.decorView.viewTreeObserver.removeOnDrawListener(onDrawListener)
         }
-        validateHorizontalScroller(10, 30)
+        validateHorizontalScroller(offset = scrollDistance, width = width)
     }
 
     @Test
@@ -226,6 +255,7 @@
             .doScrollTo()
             .assertIsDisplayed()
     }
+
     @Test
     fun horizontalScroller_scrollTo_scrollForward() {
         createScrollableContent(isVertical = false)
@@ -250,6 +280,7 @@
             .doScrollTo()
             .assertIsDisplayed()
     }
+
     @Test
     fun horizontalScroller_scrollTo_scrollBack() {
         createScrollableContent(isVertical = false)
@@ -265,13 +296,60 @@
             .assertIsDisplayed()
     }
 
+    @Test
+    fun verticalScroller_swipeUp_swipeDown() {
+        swipeScrollerAndBack(true, GestureScope::sendSwipeUp, GestureScope::sendSwipeDown)
+    }
+
+    @Test
+    fun horizontalScroller_swipeLeft_swipeRight() {
+        swipeScrollerAndBack(false, GestureScope::sendSwipeLeft, GestureScope::sendSwipeRight)
+    }
+
+    private fun swipeScrollerAndBack(
+        isVertical: Boolean,
+        firstSwipe: GestureScope.() -> Unit,
+        secondSwipe: GestureScope.() -> Unit
+    ) {
+        val scrollerPosition = ScrollerPosition()
+
+        createScrollableContent(isVertical, scrollerPosition = scrollerPosition)
+        assertThat(scrollerPosition.getValueOnUiThread()).isEqualTo(0.px)
+
+        findByTag("scroller")
+            .doGesture { firstSwipe() }
+            .awaitScrollAnimation(scrollerPosition)
+
+        val scrolledValue = scrollerPosition.getValueOnUiThread()
+        assertThat(scrolledValue).isGreaterThan(0.px)
+
+        findByTag("scroller")
+            .doGesture { secondSwipe() }
+            .awaitScrollAnimation(scrollerPosition)
+
+        assertThat(scrollerPosition.getValueOnUiThread()).isLessThan(scrolledValue)
+    }
+
+    private fun ScrollerPosition.getValueOnUiThread(): Px {
+        var value = 0.px
+        val latch = CountDownLatch(1)
+        composeTestRule.runOnUiThread {
+            value = this.value
+            latch.countDown()
+        }
+        latch.await()
+        return value
+    }
+
     private fun composeVerticalScroller(
         scrollerPosition: ScrollerPosition = ScrollerPosition(),
-        height: IntPx = 40.ipx
+        width: IntPx = defaultCrossAxisSize,
+        height: IntPx = defaultMainAxisSize,
+        rowHeight: IntPx = defaultCellSize
     ) {
         // We assume that the height of the device is more than 45 px
         withDensity(composeTestRule.density) {
-            val constraints = DpConstraints.tightConstraints(45.px.toDp(), height.toDp())
+            val constraints = DpConstraints.tightConstraints(width.toDp(), height.toDp())
             composeTestRule.runOnUiThread {
                 activity.setContent {
                     Align(alignment = Alignment.TopLeft) {
@@ -280,8 +358,8 @@
                                 Column {
                                     colors.forEach { color ->
                                         Container(
-                                            height = 5.px.toDp(),
-                                            width = 45.px.toDp()
+                                            height = rowHeight.toDp(),
+                                            width = width.toDp()
                                         ) {
                                             Draw { canvas, parentSize ->
                                                 val paint = Paint()
@@ -305,11 +383,13 @@
 
     private fun composeHorizontalScroller(
         scrollerPosition: ScrollerPosition = ScrollerPosition(),
-        width: IntPx = 40.ipx
+        width: IntPx = defaultMainAxisSize,
+        height: IntPx = defaultCrossAxisSize,
+        columnWidth: IntPx = defaultCellSize
     ) {
         // We assume that the height of the device is more than 45 px
         withDensity(composeTestRule.density) {
-            val constraints = DpConstraints.tightConstraints(width.toDp(), 45.px.toDp())
+            val constraints = DpConstraints.tightConstraints(width.toDp(), height.toDp())
             composeTestRule.runOnUiThread {
                 activity.setContent {
                     Align(alignment = Alignment.TopLeft) {
@@ -318,8 +398,8 @@
                                 Row {
                                     colors.forEach { color ->
                                         Container(
-                                            width = 5.px.toDp(),
-                                            height = 45.px.toDp()
+                                            width = columnWidth.toDp(),
+                                            height = height.toDp()
                                         ) {
                                             Draw { canvas, parentSize ->
                                                 val paint = Paint()
@@ -341,21 +421,23 @@
         }
     }
 
+    @RequiresApi(api = 26)
     private fun validateVerticalScroller(
-        offset: Int,
-        height: Int,
-        width: Int = 45
+        offset: IntPx = 0.ipx,
+        width: IntPx = 45.ipx,
+        height: IntPx = 40.ipx,
+        rowHeight: IntPx = 5.ipx
     ) {
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
         val bitmap = waitAndScreenShot()
-        assertTrue(bitmap.height >= height)
-        assertTrue(bitmap.width >= 45)
-        for (y in 0 until height) {
-            val colorIndex = (offset + y) / 5
+        assertTrue(bitmap.height >= height.value)
+        assertTrue(bitmap.width >= width.value)
+        for (y in 0 until height.value) {
+            val colorIndex = (offset.value + y) / rowHeight.value
             val expectedColor = colors[colorIndex]
 
-            for (x in 0 until width) {
+            for (x in 0 until width.value) {
                 val pixel = bitmap.getPixel(x, y)
                 assertEquals(
                     "Expected $expectedColor, but got ${Color(pixel)} at $x, $y",
@@ -365,21 +447,23 @@
         }
     }
 
+    @RequiresApi(api = 26)
     private fun validateHorizontalScroller(
-        offset: Int,
-        width: Int,
-        height: Int = 45
+        offset: IntPx = 0.ipx,
+        width: IntPx = 40.ipx,
+        height: IntPx = 45.ipx,
+        columnWidth: IntPx = 5.ipx
     ) {
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
         val bitmap = waitAndScreenShot()
-        assertTrue(bitmap.height >= 45)
-        assertTrue(bitmap.width >= width)
-        for (x in 0 until width) {
-            val colorIndex = (offset + x) / 5
+        assertTrue(bitmap.height >= height.value)
+        assertTrue(bitmap.width >= width.value)
+        for (x in 0 until width.value) {
+            val colorIndex = (offset.value + x) / columnWidth.value
             val expectedColor = colors[colorIndex]
 
-            for (y in 0 until height) {
+            for (y in 0 until height.value) {
                 val pixel = bitmap.getPixel(x, y)
                 assertEquals(
                     "Expected $expectedColor, but got ${Color(pixel)} at $x, $y",
@@ -389,25 +473,37 @@
         }
     }
 
-    private fun createScrollableContent(isVertical: Boolean) {
+    private fun createScrollableContent(
+        isVertical: Boolean,
+        itemCount: Int = 100,
+        width: Dp = 100.dp,
+        height: Dp = 100.dp,
+        scrollerPosition: ScrollerPosition = ScrollerPosition()
+    ) {
         composeTestRule.setContent {
-            val style = TextStyle(fontSize = 30.sp)
             val content = @Composable {
-                for (i in 1..100) {
-                    Text(text = i.toString(), style = style)
+                repeat(itemCount) {
+                    Text(text = "$it")
                 }
             }
-            Padding(padding = 10.dp) {
-                if (isVertical) {
-                    VerticalScroller {
-                        Column {
-                            content()
-                        }
-                    }
-                } else {
-                    HorizontalScroller {
-                        Row {
-                            content()
+            Align(alignment = Alignment.TopLeft) {
+                Container(width = width, height = height) {
+                    DrawShape(RectangleShape, Color.White)
+                    TestTag("scroller") {
+                        Semantics {
+                            if (isVertical) {
+                                VerticalScroller(scrollerPosition) {
+                                    Column {
+                                        content()
+                                    }
+                                }
+                            } else {
+                                HorizontalScroller(scrollerPosition) {
+                                    Row {
+                                        content()
+                                    }
+                                }
+                            }
                         }
                     }
                 }
@@ -415,6 +511,7 @@
         }
     }
 
+    @RequiresApi(api = 26) // For PixelCopy.request(Window, Rect, Bitmap, listener, Handler)
     private fun waitAndScreenShot(): Bitmap {
         val view = findAndroidComposeView()
         waitForDraw(view)
@@ -441,14 +538,35 @@
         return dest
     }
 
+    private fun SemanticsNodeInteraction.awaitScrollAnimation(
+        scroller: ScrollerPosition
+    ): SemanticsNodeInteraction {
+        if (!scroller.holder.animatedFloat.isRunning) {
+            return this
+        }
+        val latch = CountDownLatch(1)
+        val handler = Handler(Looper.getMainLooper())
+        handler.post(object : Runnable {
+            override fun run() {
+                if (scroller.holder.animatedFloat.isRunning) {
+                    handler.post(this)
+                } else {
+                    latch.countDown()
+                }
+            }
+        })
+        latch.await()
+        return this
+    }
+
     // TODO(malkov): ALL below is copypaste from LayoutTest as this test in ui-foundation now
 
-    internal fun findAndroidComposeView(): AndroidComposeView {
+    private fun findAndroidComposeView(): AndroidComposeView {
         val contentViewGroup = activity.findViewById<ViewGroup>(android.R.id.content)
         return findAndroidComposeView(contentViewGroup)!!
     }
 
-    internal fun findAndroidComposeView(parent: ViewGroup): AndroidComposeView? {
+    private fun findAndroidComposeView(parent: ViewGroup): AndroidComposeView? {
         for (index in 0 until parent.childCount) {
             val child = parent.getChildAt(index)
             if (child is AndroidComposeView) {
@@ -463,7 +581,7 @@
         return null
     }
 
-    internal fun waitForDraw(view: View) {
+    private fun waitForDraw(view: View) {
         val viewDrawLatch = CountDownLatch(1)
         val listener = object : ViewTreeObserver.OnDrawListener {
             override fun onDraw() {
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
index 7881172..5bb083e 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
@@ -53,7 +53,7 @@
 @Model
 class ScrollerPosition(initial: Float = 0f) {
 
-    internal val holder = AnimatedValueHolder(initial)
+    internal val holder = AnimatedValueHolder(-initial)
 
     /**
      * maxPosition this scroller that consume this ScrollerPosition can reach, or [Px.Infinity]
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeEditorModelTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeEditorModelTest.kt
index 98e68f7..58aec5d 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeEditorModelTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeEditorModelTest.kt
@@ -29,8 +29,9 @@
 import androidx.ui.input.SetSelectionEditOp
 import androidx.ui.input.TextInputService
 import androidx.ui.test.createComposeRule
-import androidx.ui.test.doClick
+import androidx.ui.test.doGesture
 import androidx.ui.test.findByTag
+import androidx.ui.test.sendClick
 import androidx.ui.test.waitForIdleCompose
 import androidx.ui.text.TextRange
 import com.google.common.truth.Truth.assertThat
@@ -90,7 +91,7 @@
 
         // Perform click to focus in.
         val element = findByTag("textField")
-        element.doClick()
+        element.doGesture { sendClick(1f, 1f) }
 
         // Verify startInput is called and capture the callback.
         val onEditCommandCaptor = argumentCaptor<(List<EditOperation>) -> Unit>()
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeFullEditorModelTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeFullEditorModelTest.kt
index c376455..bc9bb2f 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeFullEditorModelTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeFullEditorModelTest.kt
@@ -29,8 +29,9 @@
 import androidx.ui.input.SetSelectionEditOp
 import androidx.ui.input.TextInputService
 import androidx.ui.test.createComposeRule
-import androidx.ui.test.doClick
+import androidx.ui.test.doGesture
 import androidx.ui.test.findByTag
+import androidx.ui.test.sendClick
 import androidx.ui.test.waitForIdleCompose
 import androidx.ui.text.TextRange
 import com.google.common.truth.Truth.assertThat
@@ -92,7 +93,7 @@
 
         // Perform click to focus in.
         val element = findByTag("textField")
-        element.doClick()
+        element.doGesture { sendClick(1f, 1f) }
 
         // Verify startInput is called and capture the callback.
         val onEditCommandCaptor = argumentCaptor<(List<EditOperation>) -> Unit>()
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeStringTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeStringTest.kt
index 023c015..13e37b5 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeStringTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeStringTest.kt
@@ -29,8 +29,9 @@
 import androidx.ui.input.SetSelectionEditOp
 import androidx.ui.input.TextInputService
 import androidx.ui.test.createComposeRule
-import androidx.ui.test.doClick
+import androidx.ui.test.doGesture
 import androidx.ui.test.findByTag
+import androidx.ui.test.sendClick
 import androidx.ui.test.waitForIdleCompose
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.any
@@ -90,7 +91,7 @@
 
         // Perform click to focus in.
         val element = findByTag("textField")
-        element.doClick()
+        element.doGesture { sendClick(1f, 1f) }
 
         // Verify startInput is called and capture the callback.
         val onEditCommandCaptor = argumentCaptor<(List<EditOperation>) -> Unit>()
diff --git a/ui/ui-layout/api/0.1.0-dev03.txt b/ui/ui-layout/api/0.1.0-dev03.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/0.1.0-dev03.txt
+++ b/ui/ui-layout/api/0.1.0-dev03.txt
@@ -7,6 +7,40 @@
     method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class Aligned {
+    method public androidx.ui.core.LayoutModifier getBottom();
+    method public androidx.ui.core.LayoutModifier getBottomCenter();
+    method public androidx.ui.core.LayoutModifier getBottomLeft();
+    method public androidx.ui.core.LayoutModifier getBottomRight();
+    method public androidx.ui.core.LayoutModifier getCenter();
+    method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+    method public androidx.ui.core.LayoutModifier getCenterLeft();
+    method public androidx.ui.core.LayoutModifier getCenterRight();
+    method public androidx.ui.core.LayoutModifier getCenterVertically();
+    method public androidx.ui.core.LayoutModifier getEnd();
+    method public androidx.ui.core.LayoutModifier getStart();
+    method public androidx.ui.core.LayoutModifier getTop();
+    method public androidx.ui.core.LayoutModifier getTopCenter();
+    method public androidx.ui.core.LayoutModifier getTopLeft();
+    method public androidx.ui.core.LayoutModifier getTopRight();
+    property public final androidx.ui.core.LayoutModifier Bottom;
+    property public final androidx.ui.core.LayoutModifier BottomCenter;
+    property public final androidx.ui.core.LayoutModifier BottomLeft;
+    property public final androidx.ui.core.LayoutModifier BottomRight;
+    property public final androidx.ui.core.LayoutModifier Center;
+    property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+    property public final androidx.ui.core.LayoutModifier CenterLeft;
+    property public final androidx.ui.core.LayoutModifier CenterRight;
+    property public final androidx.ui.core.LayoutModifier CenterVertically;
+    property public final androidx.ui.core.LayoutModifier End;
+    property public final androidx.ui.core.LayoutModifier Start;
+    property public final androidx.ui.core.LayoutModifier Top;
+    property public final androidx.ui.core.LayoutModifier TopCenter;
+    property public final androidx.ui.core.LayoutModifier TopLeft;
+    property public final androidx.ui.core.LayoutModifier TopRight;
+    field public static final androidx.ui.layout.Aligned! INSTANCE;
+  }
+
   public final class AlignmentLineKt {
     ctor public AlignmentLineKt();
     method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/current.txt b/ui/ui-layout/api/current.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/current.txt
+++ b/ui/ui-layout/api/current.txt
@@ -7,6 +7,40 @@
     method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class Aligned {
+    method public androidx.ui.core.LayoutModifier getBottom();
+    method public androidx.ui.core.LayoutModifier getBottomCenter();
+    method public androidx.ui.core.LayoutModifier getBottomLeft();
+    method public androidx.ui.core.LayoutModifier getBottomRight();
+    method public androidx.ui.core.LayoutModifier getCenter();
+    method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+    method public androidx.ui.core.LayoutModifier getCenterLeft();
+    method public androidx.ui.core.LayoutModifier getCenterRight();
+    method public androidx.ui.core.LayoutModifier getCenterVertically();
+    method public androidx.ui.core.LayoutModifier getEnd();
+    method public androidx.ui.core.LayoutModifier getStart();
+    method public androidx.ui.core.LayoutModifier getTop();
+    method public androidx.ui.core.LayoutModifier getTopCenter();
+    method public androidx.ui.core.LayoutModifier getTopLeft();
+    method public androidx.ui.core.LayoutModifier getTopRight();
+    property public final androidx.ui.core.LayoutModifier Bottom;
+    property public final androidx.ui.core.LayoutModifier BottomCenter;
+    property public final androidx.ui.core.LayoutModifier BottomLeft;
+    property public final androidx.ui.core.LayoutModifier BottomRight;
+    property public final androidx.ui.core.LayoutModifier Center;
+    property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+    property public final androidx.ui.core.LayoutModifier CenterLeft;
+    property public final androidx.ui.core.LayoutModifier CenterRight;
+    property public final androidx.ui.core.LayoutModifier CenterVertically;
+    property public final androidx.ui.core.LayoutModifier End;
+    property public final androidx.ui.core.LayoutModifier Start;
+    property public final androidx.ui.core.LayoutModifier Top;
+    property public final androidx.ui.core.LayoutModifier TopCenter;
+    property public final androidx.ui.core.LayoutModifier TopLeft;
+    property public final androidx.ui.core.LayoutModifier TopRight;
+    field public static final androidx.ui.layout.Aligned! INSTANCE;
+  }
+
   public final class AlignmentLineKt {
     ctor public AlignmentLineKt();
     method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-layout/api/public_plus_experimental_0.1.0-dev03.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/public_plus_experimental_0.1.0-dev03.txt
+++ b/ui/ui-layout/api/public_plus_experimental_0.1.0-dev03.txt
@@ -7,6 +7,40 @@
     method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class Aligned {
+    method public androidx.ui.core.LayoutModifier getBottom();
+    method public androidx.ui.core.LayoutModifier getBottomCenter();
+    method public androidx.ui.core.LayoutModifier getBottomLeft();
+    method public androidx.ui.core.LayoutModifier getBottomRight();
+    method public androidx.ui.core.LayoutModifier getCenter();
+    method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+    method public androidx.ui.core.LayoutModifier getCenterLeft();
+    method public androidx.ui.core.LayoutModifier getCenterRight();
+    method public androidx.ui.core.LayoutModifier getCenterVertically();
+    method public androidx.ui.core.LayoutModifier getEnd();
+    method public androidx.ui.core.LayoutModifier getStart();
+    method public androidx.ui.core.LayoutModifier getTop();
+    method public androidx.ui.core.LayoutModifier getTopCenter();
+    method public androidx.ui.core.LayoutModifier getTopLeft();
+    method public androidx.ui.core.LayoutModifier getTopRight();
+    property public final androidx.ui.core.LayoutModifier Bottom;
+    property public final androidx.ui.core.LayoutModifier BottomCenter;
+    property public final androidx.ui.core.LayoutModifier BottomLeft;
+    property public final androidx.ui.core.LayoutModifier BottomRight;
+    property public final androidx.ui.core.LayoutModifier Center;
+    property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+    property public final androidx.ui.core.LayoutModifier CenterLeft;
+    property public final androidx.ui.core.LayoutModifier CenterRight;
+    property public final androidx.ui.core.LayoutModifier CenterVertically;
+    property public final androidx.ui.core.LayoutModifier End;
+    property public final androidx.ui.core.LayoutModifier Start;
+    property public final androidx.ui.core.LayoutModifier Top;
+    property public final androidx.ui.core.LayoutModifier TopCenter;
+    property public final androidx.ui.core.LayoutModifier TopLeft;
+    property public final androidx.ui.core.LayoutModifier TopRight;
+    field public static final androidx.ui.layout.Aligned! INSTANCE;
+  }
+
   public final class AlignmentLineKt {
     ctor public AlignmentLineKt();
     method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/public_plus_experimental_current.txt b/ui/ui-layout/api/public_plus_experimental_current.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/public_plus_experimental_current.txt
+++ b/ui/ui-layout/api/public_plus_experimental_current.txt
@@ -7,6 +7,40 @@
     method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class Aligned {
+    method public androidx.ui.core.LayoutModifier getBottom();
+    method public androidx.ui.core.LayoutModifier getBottomCenter();
+    method public androidx.ui.core.LayoutModifier getBottomLeft();
+    method public androidx.ui.core.LayoutModifier getBottomRight();
+    method public androidx.ui.core.LayoutModifier getCenter();
+    method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+    method public androidx.ui.core.LayoutModifier getCenterLeft();
+    method public androidx.ui.core.LayoutModifier getCenterRight();
+    method public androidx.ui.core.LayoutModifier getCenterVertically();
+    method public androidx.ui.core.LayoutModifier getEnd();
+    method public androidx.ui.core.LayoutModifier getStart();
+    method public androidx.ui.core.LayoutModifier getTop();
+    method public androidx.ui.core.LayoutModifier getTopCenter();
+    method public androidx.ui.core.LayoutModifier getTopLeft();
+    method public androidx.ui.core.LayoutModifier getTopRight();
+    property public final androidx.ui.core.LayoutModifier Bottom;
+    property public final androidx.ui.core.LayoutModifier BottomCenter;
+    property public final androidx.ui.core.LayoutModifier BottomLeft;
+    property public final androidx.ui.core.LayoutModifier BottomRight;
+    property public final androidx.ui.core.LayoutModifier Center;
+    property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+    property public final androidx.ui.core.LayoutModifier CenterLeft;
+    property public final androidx.ui.core.LayoutModifier CenterRight;
+    property public final androidx.ui.core.LayoutModifier CenterVertically;
+    property public final androidx.ui.core.LayoutModifier End;
+    property public final androidx.ui.core.LayoutModifier Start;
+    property public final androidx.ui.core.LayoutModifier Top;
+    property public final androidx.ui.core.LayoutModifier TopCenter;
+    property public final androidx.ui.core.LayoutModifier TopLeft;
+    property public final androidx.ui.core.LayoutModifier TopRight;
+    field public static final androidx.ui.layout.Aligned! INSTANCE;
+  }
+
   public final class AlignmentLineKt {
     ctor public AlignmentLineKt();
     method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/restricted_0.1.0-dev03.txt b/ui/ui-layout/api/restricted_0.1.0-dev03.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/restricted_0.1.0-dev03.txt
+++ b/ui/ui-layout/api/restricted_0.1.0-dev03.txt
@@ -7,6 +7,40 @@
     method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class Aligned {
+    method public androidx.ui.core.LayoutModifier getBottom();
+    method public androidx.ui.core.LayoutModifier getBottomCenter();
+    method public androidx.ui.core.LayoutModifier getBottomLeft();
+    method public androidx.ui.core.LayoutModifier getBottomRight();
+    method public androidx.ui.core.LayoutModifier getCenter();
+    method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+    method public androidx.ui.core.LayoutModifier getCenterLeft();
+    method public androidx.ui.core.LayoutModifier getCenterRight();
+    method public androidx.ui.core.LayoutModifier getCenterVertically();
+    method public androidx.ui.core.LayoutModifier getEnd();
+    method public androidx.ui.core.LayoutModifier getStart();
+    method public androidx.ui.core.LayoutModifier getTop();
+    method public androidx.ui.core.LayoutModifier getTopCenter();
+    method public androidx.ui.core.LayoutModifier getTopLeft();
+    method public androidx.ui.core.LayoutModifier getTopRight();
+    property public final androidx.ui.core.LayoutModifier Bottom;
+    property public final androidx.ui.core.LayoutModifier BottomCenter;
+    property public final androidx.ui.core.LayoutModifier BottomLeft;
+    property public final androidx.ui.core.LayoutModifier BottomRight;
+    property public final androidx.ui.core.LayoutModifier Center;
+    property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+    property public final androidx.ui.core.LayoutModifier CenterLeft;
+    property public final androidx.ui.core.LayoutModifier CenterRight;
+    property public final androidx.ui.core.LayoutModifier CenterVertically;
+    property public final androidx.ui.core.LayoutModifier End;
+    property public final androidx.ui.core.LayoutModifier Start;
+    property public final androidx.ui.core.LayoutModifier Top;
+    property public final androidx.ui.core.LayoutModifier TopCenter;
+    property public final androidx.ui.core.LayoutModifier TopLeft;
+    property public final androidx.ui.core.LayoutModifier TopRight;
+    field public static final androidx.ui.layout.Aligned! INSTANCE;
+  }
+
   public final class AlignmentLineKt {
     ctor public AlignmentLineKt();
     method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/restricted_current.txt b/ui/ui-layout/api/restricted_current.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/restricted_current.txt
+++ b/ui/ui-layout/api/restricted_current.txt
@@ -7,6 +7,40 @@
     method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
+  public final class Aligned {
+    method public androidx.ui.core.LayoutModifier getBottom();
+    method public androidx.ui.core.LayoutModifier getBottomCenter();
+    method public androidx.ui.core.LayoutModifier getBottomLeft();
+    method public androidx.ui.core.LayoutModifier getBottomRight();
+    method public androidx.ui.core.LayoutModifier getCenter();
+    method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+    method public androidx.ui.core.LayoutModifier getCenterLeft();
+    method public androidx.ui.core.LayoutModifier getCenterRight();
+    method public androidx.ui.core.LayoutModifier getCenterVertically();
+    method public androidx.ui.core.LayoutModifier getEnd();
+    method public androidx.ui.core.LayoutModifier getStart();
+    method public androidx.ui.core.LayoutModifier getTop();
+    method public androidx.ui.core.LayoutModifier getTopCenter();
+    method public androidx.ui.core.LayoutModifier getTopLeft();
+    method public androidx.ui.core.LayoutModifier getTopRight();
+    property public final androidx.ui.core.LayoutModifier Bottom;
+    property public final androidx.ui.core.LayoutModifier BottomCenter;
+    property public final androidx.ui.core.LayoutModifier BottomLeft;
+    property public final androidx.ui.core.LayoutModifier BottomRight;
+    property public final androidx.ui.core.LayoutModifier Center;
+    property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+    property public final androidx.ui.core.LayoutModifier CenterLeft;
+    property public final androidx.ui.core.LayoutModifier CenterRight;
+    property public final androidx.ui.core.LayoutModifier CenterVertically;
+    property public final androidx.ui.core.LayoutModifier End;
+    property public final androidx.ui.core.LayoutModifier Start;
+    property public final androidx.ui.core.LayoutModifier Top;
+    property public final androidx.ui.core.LayoutModifier TopCenter;
+    property public final androidx.ui.core.LayoutModifier TopLeft;
+    property public final androidx.ui.core.LayoutModifier TopRight;
+    field public static final androidx.ui.layout.Aligned! INSTANCE;
+  }
+
   public final class AlignmentLineKt {
     ctor public AlignmentLineKt();
     method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/AlignSample.kt b/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/AlignSample.kt
index 0e94d80..69af541 100644
--- a/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/AlignSample.kt
+++ b/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/AlignSample.kt
@@ -22,6 +22,7 @@
 import androidx.ui.core.dp
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Align
+import androidx.ui.layout.Aligned
 import androidx.ui.layout.Center
 import androidx.ui.layout.Column
 import androidx.ui.layout.ExpandedHeight
@@ -47,6 +48,18 @@
 
 @Sampled
 @Composable
+fun SimpleAlignedModifier() {
+    SizedRectangle(modifier = Aligned.TopCenter, color = Color.Blue, width = 20.dp, height = 20.dp)
+}
+
+@Sampled
+@Composable
+fun SimpleVerticallyAlignedModifier() {
+    SizedRectangle(modifier = Aligned.CenterVertically, color = Color.Blue, height = 50.dp)
+}
+
+@Sampled
+@Composable
 fun SimpleGravityInRow() {
     Row(ExpandedHeight) {
         // The child with no gravity modifier is positioned by default so that its top edge is
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
index 1442e18..2de62fa 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
@@ -32,8 +32,12 @@
 import androidx.ui.core.px
 import androidx.ui.core.withDensity
 import androidx.ui.layout.Align
+import androidx.ui.layout.Aligned
 import androidx.ui.layout.AspectRatio
 import androidx.ui.layout.Container
+import androidx.ui.layout.ExpandedHeight
+import androidx.ui.layout.Size
+import androidx.ui.layout.Width
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -85,6 +89,83 @@
     }
 
     @Test
+    fun test2DAlignedModifier() = withDensity(density) {
+        val sizeDp = 50.dp
+        val size = sizeDp.toIntPx()
+
+        val positionedLatch = CountDownLatch(2)
+        val alignSize = Ref<PxSize>()
+        val alignPosition = Ref<PxPosition>()
+        val childSize = Ref<PxSize>()
+        val childPosition = Ref<PxPosition>()
+        show {
+            Container {
+                SaveLayoutInfo(
+                    size = alignSize,
+                    position = alignPosition,
+                    positionedLatch = positionedLatch
+                )
+                Container(modifier = Aligned.BottomRight wraps Size(sizeDp, sizeDp)) {
+                    SaveLayoutInfo(
+                        size = childSize,
+                        position = childPosition,
+                        positionedLatch = positionedLatch
+                    )
+                }
+            }
+        }
+        positionedLatch.await(1, TimeUnit.SECONDS)
+
+        val root = findAndroidComposeView()
+        waitForDraw(root)
+
+        assertEquals(PxSize(root.width.px, root.height.px), alignSize.value)
+        assertEquals(PxPosition(0.px, 0.px), alignPosition.value)
+        assertEquals(PxSize(size, size), childSize.value)
+        assertEquals(
+            PxPosition(root.width.px - size, root.height.px - size),
+            childPosition.value
+        )
+    }
+
+    @Test
+    fun test1DAlignedModifier() = withDensity(density) {
+        val sizeDp = 50.dp
+        val size = sizeDp.toIntPx()
+
+        val positionedLatch = CountDownLatch(2)
+        val alignSize = Ref<PxSize>()
+        val alignPosition = Ref<PxPosition>()
+        val childSize = Ref<PxSize>()
+        val childPosition = Ref<PxPosition>()
+        show {
+            Container {
+                SaveLayoutInfo(
+                    size = alignSize,
+                    position = alignPosition,
+                    positionedLatch = positionedLatch
+                )
+                Container(modifier = Aligned.End wraps ExpandedHeight wraps Width(sizeDp)) {
+                    SaveLayoutInfo(
+                        size = childSize,
+                        position = childPosition,
+                        positionedLatch = positionedLatch
+                    )
+                }
+            }
+        }
+        positionedLatch.await(1, TimeUnit.SECONDS)
+
+        val root = findAndroidComposeView()
+        waitForDraw(root)
+
+        assertEquals(PxSize(root.width.px, root.height.px), alignSize.value)
+        assertEquals(PxPosition(0.px, 0.px), alignPosition.value)
+        assertEquals(PxSize(size, root.height.ipx), childSize.value)
+        assertEquals(PxPosition(root.width.px - size, 0.px), childPosition.value)
+    }
+
+    @Test
     fun testAlign_wrapsContent_whenMeasuredWithInfiniteConstraints() = withDensity(density) {
         val sizeDp = 50.dp
         val size = sizeDp.toIntPx()
@@ -131,6 +212,55 @@
         assertEquals(PxPosition(0.px, 0.px), childPosition.value)
     }
 
+    @Test
+    fun testAlignedModifier_wrapsContent_whenMeasuredWithInfiniteConstraints() = withDensity(
+        density
+    ) {
+        val sizeDp = 50.dp
+        val size = sizeDp.toIntPx()
+
+        val positionedLatch = CountDownLatch(2)
+        val alignSize = Ref<PxSize>()
+        val alignPosition = Ref<PxPosition>()
+        val childSize = Ref<PxSize>()
+        val childPosition = Ref<PxPosition>()
+        show {
+            Layout(
+                children = {
+                    Container {
+                        SaveLayoutInfo(
+                            size = alignSize,
+                            position = alignPosition,
+                            positionedLatch = positionedLatch
+                        )
+                        Container(modifier = Aligned.BottomRight wraps Size(sizeDp, sizeDp)) {
+                            SaveLayoutInfo(
+                                size = childSize,
+                                position = childPosition,
+                                positionedLatch = positionedLatch
+                            )
+                        }
+                    }
+                },
+                measureBlock = { measurables, constraints ->
+                    val placeable = measurables.first().measure(Constraints())
+                    layout(constraints.maxWidth, constraints.maxHeight) {
+                        placeable.place(0.ipx, 0.ipx)
+                    }
+                }
+            )
+        }
+        positionedLatch.await(1, TimeUnit.SECONDS)
+
+        val root = findAndroidComposeView()
+        waitForDraw(root)
+
+        assertEquals(PxSize(size, size), alignSize.value)
+        assertEquals(PxPosition(0.px, 0.px), alignPosition.value)
+        assertEquals(PxSize(size, size), childSize.value)
+        assertEquals(PxPosition(0.px, 0.px), childPosition.value)
+    }
+
     // TODO(popam): this should be unit test instead
     @Test
     fun testAlignmentCoordinates_evenSize() {
@@ -184,6 +314,61 @@
     }
 
     @Test
+    fun test2DAlignedModifier_hasCorrectIntrinsicMeasurements() = withDensity(density) {
+        testIntrinsics(@Composable {
+            Container(Aligned.TopLeft wraps AspectRatio(2f)) { }
+        }) { minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight ->
+            // Min width.
+            assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+            assertEquals(25.dp.toIntPx() * 2, minIntrinsicWidth(25.dp.toIntPx()))
+            assertEquals(0.dp.toIntPx(), minIntrinsicWidth(IntPx.Infinity))
+
+            // Min height.
+            assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+            assertEquals(50.dp.toIntPx() / 2, minIntrinsicHeight(50.dp.toIntPx()))
+            assertEquals(0.dp.toIntPx(), minIntrinsicHeight(IntPx.Infinity))
+
+            // Max width.
+            assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+            assertEquals(25.dp.toIntPx() * 2, maxIntrinsicWidth(25.dp.toIntPx()))
+            assertEquals(0.dp.toIntPx(), maxIntrinsicWidth(IntPx.Infinity))
+
+            // Max height.
+            assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+            assertEquals(50.dp.toIntPx() / 2, maxIntrinsicHeight(50.dp.toIntPx()))
+            assertEquals(0.dp.toIntPx(), maxIntrinsicHeight(IntPx.Infinity))
+        }
+    }
+
+    @Test
+    fun test1DAlignedModifier_hasCorrectIntrinsicMeasurements() = withDensity(density) {
+        testIntrinsics(@Composable {
+            Container(Aligned.CenterVertically wraps AspectRatio(2f)) { }
+        }) { minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight ->
+
+            // Min width.
+            assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+            assertEquals(25.dp.toIntPx() * 2, minIntrinsicWidth(25.dp.toIntPx()))
+            assertEquals(0.dp.toIntPx(), minIntrinsicWidth(IntPx.Infinity))
+
+            // Min height.
+            assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+            assertEquals(50.dp.toIntPx() / 2, minIntrinsicHeight(50.dp.toIntPx()))
+            assertEquals(0.dp.toIntPx(), minIntrinsicHeight(IntPx.Infinity))
+
+            // Max width.
+            assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+            assertEquals(25.dp.toIntPx() * 2, maxIntrinsicWidth(25.dp.toIntPx()))
+            assertEquals(0.dp.toIntPx(), maxIntrinsicWidth(IntPx.Infinity))
+
+            // Max height.
+            assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+            assertEquals(50.dp.toIntPx() / 2, maxIntrinsicHeight(50.dp.toIntPx()))
+            assertEquals(0.dp.toIntPx(), maxIntrinsicHeight(IntPx.Infinity))
+        }
+    }
+
+    @Test
     fun testAlign_hasCorrectIntrinsicMeasurements_whenNoChildren() = withDensity(density) {
         testIntrinsics(@Composable {
             Align(alignment = Alignment.TopLeft) { }
@@ -257,4 +442,61 @@
             childPosition.value
         )
     }
+
+    @Test
+    fun testAlignedModifier_alignsCorrectly_whenOddDimensions_endAligned() = withDensity(density) {
+        // Given a 100 x 100 pixel container, we want to make sure that when aligning a 1 x 1 pixel
+        // child to both ends (bottom, and right) we correctly position children at the last
+        // possible pixel, and avoid rounding issues. Previously we first centered the coordinates,
+        // and then aligned after, so the maths would actually be (99 / 2) * 2, which incorrectly
+        // ends up at 100 (IntPx rounds up) - so the last pixels in both directions just wouldn't
+        // be visible.
+        val parentSize = 100.ipx.toDp()
+        val childSizeDp = 1.ipx.toDp()
+        val childSizeIpx = childSizeDp.toIntPx()
+
+        val positionedLatch = CountDownLatch(2)
+        val alignSize = Ref<PxSize>()
+        val alignPosition = Ref<PxPosition>()
+        val childSize = Ref<PxSize>()
+        val childPosition = Ref<PxPosition>()
+        show {
+            Layout(
+                children = {
+                    Container(Size(parentSize, parentSize)) {
+                        SaveLayoutInfo(
+                            size = alignSize,
+                            position = alignPosition,
+                            positionedLatch = positionedLatch
+                        )
+                        Container(Aligned.BottomRight wraps Size(childSizeDp, childSizeDp)) {
+                            SaveLayoutInfo(
+                                size = childSize,
+                                position = childPosition,
+                                positionedLatch = positionedLatch
+                            )
+                        }
+                    }
+                }, measureBlock = { measurables, constraints ->
+                    val placeable = measurables.first().measure(Constraints())
+                    layout(constraints.maxWidth, constraints.maxHeight) {
+                        placeable.place(0.ipx, 0.ipx)
+                    }
+                }
+            )
+        }
+        positionedLatch.await(1, TimeUnit.SECONDS)
+
+        val root = findAndroidComposeView()
+        waitForDraw(root)
+
+        assertEquals(PxSize(childSizeIpx, childSizeIpx), childSize.value)
+        assertEquals(
+            PxPosition(
+                alignSize.value!!.width - childSizeIpx,
+                alignSize.value!!.height - childSizeIpx
+            ),
+            childPosition.value
+        )
+    }
 }
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
index d272906..fd7b567 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
@@ -22,6 +22,11 @@
 import androidx.ui.core.looseMin
 import androidx.compose.Composable
 import androidx.ui.core.Alignment
+import androidx.ui.core.Constraints
+import androidx.ui.core.DensityScope
+import androidx.ui.core.IntPxPosition
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.ipx
 
 /**
  * A layout that takes a child and aligns it within itself, according to the alignment parameter.
@@ -90,7 +95,7 @@
  * by the parent layout rather than the child itself. Different layout models allow different
  * [Gravity] options. For example, [Row] provides Top and Bottom, while [Column] provides
  * Start and End.
- * Unlike [Align], layout children with [Gravity] are aligned only after the size
+ * Unlike [Aligned], layout children with [Gravity] are aligned only after the size
  * of the parent is known, therefore not affecting the size of the parent in order to achieve
  * their own alignment.
  *
@@ -101,3 +106,161 @@
  * @sample androidx.ui.layout.samples.SimpleGravityInColumn
  */
 object Gravity
+
+/**
+ * Provides alignment options for a target layout where the alignment is handled by the modifier
+ * itself (rather than by the layout's parent). To achieve this, the modifier tries to fill the
+ * available space and align the target layout within itself. If the incoming constraints are
+ * infinite, the modifier will wrap the child instead and the alignment will not be achieved.
+ *
+ * Example usage:
+ *
+ * @sample androidx.ui.layout.samples.SimpleAlignedModifier
+ * @sample androidx.ui.layout.samples.SimpleVerticallyAlignedModifier
+ */
+object Aligned {
+    /**
+     * A layout modifier that positions the target component inside its parent to the top in
+     * vertical direction and wraps the component in horizontal direction.
+     */
+    val Top: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.TopLeft, direction = Direction.Vertical)
+
+    /**
+     * A layout modifier that positions the target component in the center of the parent in
+     * vertical direction and wraps the component in horizontal direction.
+     */
+    val CenterVertically: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.CenterLeft, direction = Direction.Vertical)
+
+    /**
+     * A layout modifier that positions the target component inside its parent to the bottom in
+     * vertical direction and wraps the component in horizontal direction.
+     */
+    val Bottom: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.BottomLeft, direction = Direction.Vertical)
+
+    /**
+     * A layout modifier that positions the target component inside its parent to the start edge
+     * in horizontal direction and wraps the component in vertical direction.
+     */
+    val Start: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.TopLeft, direction = Direction.Horizontal)
+
+    /**
+     * A layout modifier that positions the target component in the center of the parent in
+     * horizontal direction and wraps the component in vertical direction.
+     */
+    val CenterHorizontally: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.TopCenter, direction = Direction.Horizontal)
+
+    /**
+     * A layout modifier that positions the target component inside its parent to the end edge
+     * in horizontal direction and wraps the component in vertical direction.
+     */
+    val End: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.TopRight, direction = Direction.Horizontal)
+
+    /**
+     * A layout modifier that positions the target component top-left inside its parent.
+     */
+    val TopLeft: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.TopLeft, direction = Direction.Both)
+
+    /**
+     * A layout modifier that positions the target component top-center inside its parent.
+     */
+    val TopCenter: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.TopCenter, direction = Direction.Both)
+
+    /**
+     * A layout modifier that positions the target component top-right inside its parent.
+     */
+    val TopRight: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.TopRight, direction = Direction.Both)
+
+    /**
+     * A layout modifier that positions the target component center-left inside its parent.
+     */
+    val CenterLeft: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.CenterLeft, direction = Direction.Both)
+
+    /**
+     * A layout modifier that positions the target component in the center of its parent.
+     */
+    val Center: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.Center, direction = Direction.Both)
+
+    /**
+     * A layout modifier that positions the target component center-right inside its parent.
+     */
+    val CenterRight: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.CenterRight, direction = Direction.Both)
+
+    /**
+     * A layout modifier that positions the target component bottom-left inside its parent.
+     */
+    val BottomLeft: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.BottomLeft, direction = Direction.Both)
+
+    /**
+     * A layout modifier that positions the target component bottom-center inside its parent.
+     */
+    val BottomCenter: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.BottomCenter, direction = Direction.Both)
+
+    /**
+     * A layout modifier that positions the target component bottom-right inside its parent.
+     */
+    val BottomRight: LayoutModifier =
+        AlignmentModifier(alignment = Alignment.BottomRight, direction = Direction.Both)
+}
+
+private enum class Direction {
+    Vertical, Horizontal, Both
+}
+
+private data class AlignmentModifier(
+    private val alignment: Alignment,
+    private val direction: Direction
+) : LayoutModifier {
+    override fun DensityScope.modifyConstraints(constraints: Constraints) = when (direction) {
+        Direction.Both -> constraints.looseMin()
+        Direction.Horizontal -> constraints.copy(minWidth = 0.ipx)
+        Direction.Vertical -> constraints.copy(minHeight = 0.ipx)
+    }
+
+    override fun DensityScope.modifySize(
+        constraints: Constraints,
+        childSize: IntPxSize
+    ): IntPxSize {
+        val width = if (
+            direction != Direction.Vertical && constraints.maxWidth.isFinite()
+        ) {
+            constraints.maxWidth
+        } else {
+            childSize.width
+        }
+        val height = if (
+            direction != Direction.Horizontal && constraints.maxHeight.isFinite()
+        ) {
+            constraints.maxHeight
+        } else {
+            childSize.height
+        }
+        return IntPxSize(width, height)
+    }
+
+    override fun DensityScope.modifyPosition(
+        childPosition: IntPxPosition,
+        childSize: IntPxSize,
+        containerSize: IntPxSize
+    ): IntPxPosition {
+        return alignment.align(
+            IntPxSize(
+                containerSize.width - childSize.width,
+                containerSize.height - childSize.height
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-material/api/0.1.0-dev03.txt b/ui/ui-material/api/0.1.0-dev03.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/0.1.0-dev03.txt
+++ b/ui/ui-material/api/0.1.0-dev03.txt
@@ -70,7 +70,8 @@
 
   public final class ColorKt {
     ctor public ColorKt();
-    method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+    method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+    method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
   }
 
   public interface ColorPalette {
@@ -86,8 +87,10 @@
     method public androidx.ui.graphics.Color getSecondary();
     method public androidx.ui.graphics.Color getSecondaryVariant();
     method public androidx.ui.graphics.Color getSurface();
+    method public boolean isLight();
     property public abstract androidx.ui.graphics.Color background;
     property public abstract androidx.ui.graphics.Color error;
+    property public abstract boolean isLight;
     property public abstract androidx.ui.graphics.Color onBackground;
     property public abstract androidx.ui.graphics.Color onError;
     property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -70,7 +70,8 @@
 
   public final class ColorKt {
     ctor public ColorKt();
-    method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+    method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+    method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
   }
 
   public interface ColorPalette {
@@ -86,8 +87,10 @@
     method public androidx.ui.graphics.Color getSecondary();
     method public androidx.ui.graphics.Color getSecondaryVariant();
     method public androidx.ui.graphics.Color getSurface();
+    method public boolean isLight();
     property public abstract androidx.ui.graphics.Color background;
     property public abstract androidx.ui.graphics.Color error;
+    property public abstract boolean isLight;
     property public abstract androidx.ui.graphics.Color onBackground;
     property public abstract androidx.ui.graphics.Color onError;
     property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-material/api/public_plus_experimental_0.1.0-dev03.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/public_plus_experimental_0.1.0-dev03.txt
+++ b/ui/ui-material/api/public_plus_experimental_0.1.0-dev03.txt
@@ -70,7 +70,8 @@
 
   public final class ColorKt {
     ctor public ColorKt();
-    method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+    method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+    method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
   }
 
   public interface ColorPalette {
@@ -86,8 +87,10 @@
     method public androidx.ui.graphics.Color getSecondary();
     method public androidx.ui.graphics.Color getSecondaryVariant();
     method public androidx.ui.graphics.Color getSurface();
+    method public boolean isLight();
     property public abstract androidx.ui.graphics.Color background;
     property public abstract androidx.ui.graphics.Color error;
+    property public abstract boolean isLight;
     property public abstract androidx.ui.graphics.Color onBackground;
     property public abstract androidx.ui.graphics.Color onError;
     property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/public_plus_experimental_current.txt b/ui/ui-material/api/public_plus_experimental_current.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/public_plus_experimental_current.txt
+++ b/ui/ui-material/api/public_plus_experimental_current.txt
@@ -70,7 +70,8 @@
 
   public final class ColorKt {
     ctor public ColorKt();
-    method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+    method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+    method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
   }
 
   public interface ColorPalette {
@@ -86,8 +87,10 @@
     method public androidx.ui.graphics.Color getSecondary();
     method public androidx.ui.graphics.Color getSecondaryVariant();
     method public androidx.ui.graphics.Color getSurface();
+    method public boolean isLight();
     property public abstract androidx.ui.graphics.Color background;
     property public abstract androidx.ui.graphics.Color error;
+    property public abstract boolean isLight;
     property public abstract androidx.ui.graphics.Color onBackground;
     property public abstract androidx.ui.graphics.Color onError;
     property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/restricted_0.1.0-dev03.txt b/ui/ui-material/api/restricted_0.1.0-dev03.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/restricted_0.1.0-dev03.txt
+++ b/ui/ui-material/api/restricted_0.1.0-dev03.txt
@@ -70,7 +70,8 @@
 
   public final class ColorKt {
     ctor public ColorKt();
-    method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+    method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+    method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
   }
 
   public interface ColorPalette {
@@ -86,8 +87,10 @@
     method public androidx.ui.graphics.Color getSecondary();
     method public androidx.ui.graphics.Color getSecondaryVariant();
     method public androidx.ui.graphics.Color getSurface();
+    method public boolean isLight();
     property public abstract androidx.ui.graphics.Color background;
     property public abstract androidx.ui.graphics.Color error;
+    property public abstract boolean isLight;
     property public abstract androidx.ui.graphics.Color onBackground;
     property public abstract androidx.ui.graphics.Color onError;
     property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -70,7 +70,8 @@
 
   public final class ColorKt {
     ctor public ColorKt();
-    method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+    method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+    method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
   }
 
   public interface ColorPalette {
@@ -86,8 +87,10 @@
     method public androidx.ui.graphics.Color getSecondary();
     method public androidx.ui.graphics.Color getSecondaryVariant();
     method public androidx.ui.graphics.Color getSurface();
+    method public boolean isLight();
     property public abstract androidx.ui.graphics.Color background;
     property public abstract androidx.ui.graphics.Color error;
+    property public abstract boolean isLight;
     property public abstract androidx.ui.graphics.Color onBackground;
     property public abstract androidx.ui.graphics.Color onError;
     property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
index cbc2338..69edd80 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
@@ -22,9 +22,13 @@
 import androidx.ui.core.Alignment
 import androidx.ui.core.Text
 import androidx.ui.core.dp
+import androidx.ui.graphics.imageFromResource
+import androidx.ui.layout.Arrangement
 import androidx.ui.layout.Column
 import androidx.ui.layout.Container
+import androidx.ui.layout.ExpandedHeight
 import androidx.ui.layout.FlexColumn
+import androidx.ui.material.MaterialTheme
 import androidx.ui.material.RadioGroup
 import androidx.ui.material.demos.AppBarActivity.BottomAppBarOption.CenterFab
 import androidx.ui.material.demos.AppBarActivity.BottomAppBarOption.CutoutFab
@@ -34,19 +38,14 @@
 import androidx.ui.material.demos.AppBarActivity.BottomAppBarOption.NoFab
 import androidx.ui.material.demos.AppBarActivity.TopAppBarOption.Actions
 import androidx.ui.material.demos.AppBarActivity.TopAppBarOption.Simple
-import androidx.ui.material.samples.SimpleBottomAppBarCutoutFab
 import androidx.ui.material.samples.SimpleBottomAppBarCenterFab
+import androidx.ui.material.samples.SimpleBottomAppBarCutoutFab
 import androidx.ui.material.samples.SimpleBottomAppBarEndFab
 import androidx.ui.material.samples.SimpleBottomAppBarExtendedCutoutFab
 import androidx.ui.material.samples.SimpleBottomAppBarFancyAnimatingCutoutFab
 import androidx.ui.material.samples.SimpleBottomAppBarNoFab
 import androidx.ui.material.samples.SimpleTopAppBarNavIcon
 import androidx.ui.material.samples.SimpleTopAppBarNavIconWithActions
-import androidx.ui.material.surface.Surface
-import androidx.ui.graphics.imageFromResource
-import androidx.ui.layout.Arrangement
-import androidx.ui.layout.ExpandedHeight
-import androidx.ui.material.MaterialTheme
 
 class AppBarActivity : MaterialDemoActivity() {
 
@@ -77,65 +76,63 @@
         var selectedTopAppBar by +state { Simple }
         var selectedBottomAppBar by +state { NoFab }
 
-        Surface {
-            FlexColumn {
-                inflexible {
-                    Container(height = 120.dp, alignment = Alignment.TopCenter) {
-                        when (selectedTopAppBar) {
-                            Simple -> SimpleTopAppBarNavIcon(navigationImage)
-                            Actions -> SimpleTopAppBarNavIconWithActions(
-                                favouriteImage,
-                                navigationImage
+        FlexColumn {
+            inflexible {
+                Container(height = 120.dp, alignment = Alignment.TopCenter) {
+                    when (selectedTopAppBar) {
+                        Simple -> SimpleTopAppBarNavIcon(navigationImage)
+                        Actions -> SimpleTopAppBarNavIconWithActions(
+                            favouriteImage,
+                            navigationImage
+                        )
+                    }
+                }
+            }
+            flexible(1f) {
+                Column(ExpandedHeight, arrangement = Arrangement.SpaceBetween) {
+                    DemoText("TopAppBar options")
+                    RadioGroup {
+                        topAppBarOptions.forEach { topAppBar ->
+                            RadioGroupTextItem(
+                                selected = (topAppBar == selectedTopAppBar),
+                                onSelect = { selectedTopAppBar = topAppBar },
+                                text = topAppBar.description
+                            )
+                        }
+                    }
+                    DemoText("BottomAppBar options")
+                    RadioGroup {
+                        bottomAppBarOptions.forEach { bottomAppBar ->
+                            RadioGroupTextItem(
+                                selected = (bottomAppBar == selectedBottomAppBar),
+                                onSelect = { selectedBottomAppBar = bottomAppBar },
+                                text = bottomAppBar.description
                             )
                         }
                     }
                 }
-                flexible(1f) {
-                    Column(ExpandedHeight, arrangement = Arrangement.SpaceBetween) {
-                        DemoText("TopAppBar options")
-                        RadioGroup {
-                            topAppBarOptions.forEach { topAppBar ->
-                                RadioGroupTextItem(
-                                    selected = (topAppBar == selectedTopAppBar),
-                                    onSelect = { selectedTopAppBar = topAppBar },
-                                    text = topAppBar.description
-                                )
-                            }
-                        }
-                        DemoText("BottomAppBar options")
-                        RadioGroup {
-                            bottomAppBarOptions.forEach { bottomAppBar ->
-                                RadioGroupTextItem(
-                                    selected = (bottomAppBar == selectedBottomAppBar),
-                                    onSelect = { selectedBottomAppBar = bottomAppBar },
-                                    text = bottomAppBar.description
-                                )
-                            }
-                        }
-                    }
-                }
-                inflexible {
-                    Container(height = 120.dp, alignment = Alignment.BottomCenter) {
-                        when (selectedBottomAppBar) {
-                            NoFab -> SimpleBottomAppBarNoFab(favouriteImage, navigationImage)
-                            CenterFab -> SimpleBottomAppBarCenterFab(
-                                favouriteImage,
-                                navigationImage
-                            )
-                            EndFab -> SimpleBottomAppBarEndFab(favouriteImage)
-                            CutoutFab -> SimpleBottomAppBarCutoutFab(
-                                favouriteImage,
-                                navigationImage
-                            )
-                            ExtendedCutoutFab -> SimpleBottomAppBarExtendedCutoutFab(
-                                favouriteImage,
-                                navigationImage
-                            )
-                            FancyAnimatingCutoutFab -> SimpleBottomAppBarFancyAnimatingCutoutFab(
-                                favouriteImage,
-                                navigationImage
-                            )
-                        }
+            }
+            inflexible {
+                Container(height = 120.dp, alignment = Alignment.BottomCenter) {
+                    when (selectedBottomAppBar) {
+                        NoFab -> SimpleBottomAppBarNoFab(favouriteImage, navigationImage)
+                        CenterFab -> SimpleBottomAppBarCenterFab(
+                            favouriteImage,
+                            navigationImage
+                        )
+                        EndFab -> SimpleBottomAppBarEndFab(favouriteImage)
+                        CutoutFab -> SimpleBottomAppBarCutoutFab(
+                            favouriteImage,
+                            navigationImage
+                        )
+                        ExtendedCutoutFab -> SimpleBottomAppBarExtendedCutoutFab(
+                            favouriteImage,
+                            navigationImage
+                        )
+                        FancyAnimatingCutoutFab -> SimpleBottomAppBarFancyAnimatingCutoutFab(
+                            favouriteImage,
+                            navigationImage
+                        )
                     }
                 }
             }
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DataTableActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DataTableActivity.kt
index 8d92f88..0c04a5a 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DataTableActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DataTableActivity.kt
@@ -18,14 +18,11 @@
 
 import androidx.compose.Composable
 import androidx.ui.material.samples.SimpleDataTable
-import androidx.ui.material.surface.Surface
 
 class DataTableActivity : MaterialDemoActivity() {
 
     @Composable
     override fun materialContent() {
-        Surface {
-            SimpleDataTable()
-        }
+        SimpleDataTable()
     }
 }
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
index 484f12f..ec30f46 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
@@ -50,6 +50,7 @@
 import androidx.ui.material.FloatingActionButton
 import androidx.ui.material.MaterialTheme
 import androidx.ui.material.TopAppBar
+import androidx.ui.material.lightColorPalette
 import androidx.ui.material.surface.Surface
 import androidx.ui.text.TextStyle
 import kotlin.math.round
@@ -166,7 +167,7 @@
     val secondary = lerp(Color(0xFF03DAC6), Color(0xFFBB86FC), interpolatedFraction)
     val background = lerp(Color.White, Color(0xFF121212), interpolatedFraction)
 
-    return ColorPalette(
+    return lightColorPalette(
         primary = primary,
         secondary = secondary,
         background = background
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
index 98ccd23..4a91328a 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
@@ -26,21 +26,29 @@
 import androidx.compose.Composable
 import androidx.compose.FrameManager
 import androidx.compose.Model
+import androidx.compose.unaryPlus
 import androidx.preference.EditTextPreference
+import androidx.preference.PreferenceCategory
 import androidx.preference.PreferenceFragmentCompat
 import androidx.preference.PreferenceManager.getDefaultSharedPreferences
 import androidx.ui.core.setContent
+import androidx.ui.foundation.isSystemInDarkTheme
 import androidx.ui.graphics.Color
 import androidx.ui.graphics.toArgb
 import androidx.ui.material.ColorPalette
 import androidx.ui.material.MaterialTheme
+import androidx.ui.material.darkColorPalette
 import androidx.ui.material.demos.MaterialSettingsActivity.SettingsFragment
-import kotlin.random.Random
+import androidx.ui.material.lightColorPalette
+import androidx.ui.material.surface.Surface
 import kotlin.reflect.full.memberProperties
 
 @Model
 class CurrentColorPalette {
-    var colors: ColorPalette = ColorPalette()
+    var lightColors: ColorPalette = lightColorPalette()
+    var darkColors: ColorPalette = darkColorPalette()
+
+    val colors get() = if (+isSystemInDarkTheme()) darkColors else lightColors
 }
 
 /**
@@ -49,16 +57,18 @@
  */
 abstract class MaterialDemoActivity : Activity() {
 
-    private val currentColors = CurrentColorPalette()
+    private var currentColors = CurrentColorPalette()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         // Ensure we are in a frame, as this is only normally initialized after the setContent call
         FrameManager.ensureStarted()
-        currentColors.colors = getColorsFromSharedPreferences()
+        currentColors.getColorsFromSharedPreferences()
         setContent {
             MaterialTheme(currentColors.colors) {
-                materialContent()
+                Surface {
+                    materialContent()
+                }
             }
         }
     }
@@ -66,14 +76,15 @@
     override fun onResume() {
         super.onResume()
         // Update colors in case we changed something in settings activity
-        currentColors.colors = getColorsFromSharedPreferences()
+        currentColors.getColorsFromSharedPreferences()
     }
 
     override fun onCreateOptionsMenu(menu: Menu?): Boolean {
-        menu?.add(Menu.NONE, SETTINGS, Menu.NONE, "Theme settings")
-        menu?.add(Menu.NONE, SHUFFLE, Menu.NONE, "Shuffle colors")
-        menu?.add(Menu.NONE, INVERT, Menu.NONE, "Invert color mapping")
-        menu?.add(Menu.NONE, RESET, Menu.NONE, "Reset theme to default")
+        menu?.run {
+            add(Menu.NONE, SETTINGS, Menu.NONE, "Theme settings")
+            add(Menu.NONE, SHUFFLE, Menu.NONE, "Shuffle colors")
+            add(Menu.NONE, RESET, Menu.NONE, "Reset theme to default")
+        }
         return true
     }
 
@@ -81,37 +92,13 @@
         when (item.itemId) {
             SETTINGS -> startActivity(Intent(this, MaterialSettingsActivity::class.java))
             SHUFFLE -> {
-                val colors = generateColorPalette(currentColors.colors)
-                colors.saveColors()
-                currentColors.colors = colors
-            }
-            INVERT -> {
-                // Flip all colors
-                val newPrimary = currentColors.colors.onPrimary
-                val newOnPrimary = currentColors.colors.primary
-                val newSecondary = currentColors.colors.onSecondary
-                val newOnSecondary = currentColors.colors.secondary
-                val colors = ColorPalette(
-                    primary = newPrimary,
-                    primaryVariant = currentColors.colors.primaryVariant,
-                    secondary = newSecondary,
-                    secondaryVariant = currentColors.colors.secondaryVariant,
-                    background = currentColors.colors.background,
-                    surface = currentColors.colors.surface,
-                    error = currentColors.colors.error,
-                    onPrimary = newOnPrimary,
-                    onSecondary = newOnSecondary,
-                    onBackground = currentColors.colors.onBackground,
-                    onSurface = currentColors.colors.onSurface,
-                    onError = currentColors.colors.onError
-                )
-                colors.saveColors()
-                currentColors.colors = colors
+                currentColors.shuffleColors()
+                currentColors.saveColors()
             }
             RESET -> {
                 val sharedPreferences = getDefaultSharedPreferences(this)
                 sharedPreferences.edit().clear().apply()
-                currentColors.colors = getColorsFromSharedPreferences()
+                currentColors.getColorsFromSharedPreferences()
             }
         }
         return true
@@ -122,30 +109,41 @@
      * not present in the [SharedPreferences], its default value as defined in [ColorPalette]
      * will be returned.
      */
-    private fun getColorsFromSharedPreferences(): ColorPalette {
-        val sharedPreferences = getDefaultSharedPreferences(this)
-        val function = ::ColorPalette
-        val parametersToSet = function.parameters.mapNotNull { parameter ->
-            val savedValue = sharedPreferences.getString(parameter.name, "")
-            if (savedValue.isNullOrBlank()) {
-                null
-            } else {
-                val parsedColor = Color(java.lang.Long.parseLong(savedValue, 16))
-                parameter to parsedColor
-            }
-        }.toMap()
-        if (parametersToSet.isEmpty()) return ColorPalette()
-        return ::ColorPalette.callBy(parametersToSet)
+    private fun CurrentColorPalette.getColorsFromSharedPreferences() {
+        val sharedPreferences = getDefaultSharedPreferences(this@MaterialDemoActivity)
+
+        fun getColorsFromSharedPreferences(isLightTheme: Boolean): ColorPalette {
+            val function = if (isLightTheme) ::lightColorPalette else ::darkColorPalette
+            val parametersToSet = function.parameters.mapNotNull { parameter ->
+                val savedValue = sharedPreferences.getString(parameter.name + isLightTheme, "")
+                if (savedValue.isNullOrBlank()) {
+                    null
+                } else {
+                    val parsedColor = Color(java.lang.Long.parseLong(savedValue, 16))
+                    parameter to parsedColor
+                }
+            }.toMap()
+            return function.callBy(parametersToSet)
+        }
+
+        lightColors = getColorsFromSharedPreferences(true)
+        darkColors = getColorsFromSharedPreferences(false)
     }
 
     /**
-     * Persists the current [ColorPalette] to [SharedPreferences].
+     * Persists the current [CurrentColorPalette] to [SharedPreferences].
      */
-    private fun ColorPalette.saveColors() {
-        forEachColorProperty { name, color ->
+    private fun CurrentColorPalette.saveColors() {
+        lightColors.forEachColorProperty { name, color ->
             getDefaultSharedPreferences(this@MaterialDemoActivity)
                 .edit()
-                .putString(name, Integer.toHexString(color.toArgb()))
+                .putString(name + true, Integer.toHexString(color.toArgb()))
+                .apply()
+        }
+        darkColors.forEachColorProperty { name, color ->
+            getDefaultSharedPreferences(this@MaterialDemoActivity)
+                .edit()
+                .putString(name + false, Integer.toHexString(color.toArgb()))
                 .apply()
         }
     }
@@ -155,22 +153,37 @@
      * [ColorPalette.secondary] and [ColorPalette.onSecondary] as dark-on-light or light-on-dark
      * pairs.
      */
-    private fun generateColorPalette(currentColors: ColorPalette): ColorPalette {
-        val (primary, onPrimary) = generateColorPair()
-        val (secondary, onSecondary) = generateColorPair()
-        return ColorPalette(
-            primary = primary,
-            primaryVariant = currentColors.primaryVariant,
-            secondary = secondary,
-            secondaryVariant = currentColors.secondaryVariant,
-            background = currentColors.background,
-            surface = currentColors.surface,
-            error = currentColors.error,
-            onPrimary = onPrimary,
-            onSecondary = onSecondary,
-            onBackground = currentColors.onBackground,
-            onSurface = currentColors.onSurface,
-            onError = currentColors.onError
+    private fun CurrentColorPalette.shuffleColors() {
+        val (lightPrimary, lightOnPrimary) = generateColorPair(true)
+        val (lightSecondary, lightOnSecondary) = generateColorPair(true)
+        lightColors = lightColorPalette(
+            primary = lightPrimary,
+            primaryVariant = lightColors.primaryVariant,
+            secondary = lightSecondary,
+            secondaryVariant = lightColors.secondaryVariant,
+            background = lightColors.background,
+            surface = lightColors.surface,
+            error = lightColors.error,
+            onPrimary = lightOnPrimary,
+            onSecondary = lightOnSecondary,
+            onBackground = lightColors.onBackground,
+            onSurface = lightColors.onSurface,
+            onError = lightColors.onError
+        )
+        val (darkPrimary, darkOnPrimary) = generateColorPair(false)
+        val (darkSecondary, darkOnSecondary) = generateColorPair(false)
+        darkColors = darkColorPalette(
+            primary = darkPrimary,
+            primaryVariant = darkColors.primaryVariant,
+            secondary = darkSecondary,
+            background = darkColors.background,
+            surface = darkColors.surface,
+            error = darkColors.error,
+            onPrimary = darkOnPrimary,
+            onSecondary = darkOnSecondary,
+            onBackground = darkColors.onBackground,
+            onSurface = darkColors.onSurface,
+            onError = darkColors.onError
         )
     }
 
@@ -178,14 +191,13 @@
      * Generate a random dark and light color from the palette, and returns either a dark-on-light
      * or light-on-dark color pair.
      */
-    private fun generateColorPair(): Pair<Color, Color> {
+    private fun generateColorPair(isLightTheme: Boolean): Pair<Color, Color> {
         val darkColor = Color(DARK_PALETTE_COLORS.random())
         val lightColor = Color(LIGHT_PALETTE_COLORS.random())
-        val isMainColorLight = Random.nextBoolean()
-        return if (isMainColorLight) {
-            (lightColor to darkColor)
+        return if (isLightTheme) {
+            darkColor to lightColor
         } else {
-            (darkColor to lightColor)
+            lightColor to darkColor
         }
     }
 
@@ -199,8 +211,7 @@
     companion object {
         private const val SETTINGS = 1
         private const val SHUFFLE = 2
-        private const val INVERT = 3
-        private const val RESET = 4
+        private const val RESET = 3
 
         // Colors taken from https://material.io/design/color -> 2014 Material Design color palettes
 
@@ -465,15 +476,35 @@
         override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
             val context = preferenceManager.context
             val screen = preferenceManager.createPreferenceScreen(context)
+
+            val light = PreferenceCategory(context).apply {
+                title = "Light colors"
+                screen.addPreference(this)
+            }
             // Create new ColorPalette to resolve defaults
-            ColorPalette().forEachColorProperty { name, color ->
+            lightColorPalette().forEachColorProperty { name, color ->
                 val preference = EditTextPreference(context)
-                preference.key = name
+                preference.key = name + true
                 preference.title = name
                 // set the default value to be the default for ColorPalette
                 preference.setDefaultValue(Integer.toHexString(color.toArgb()))
                 preference.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
-                screen.addPreference(preference)
+                light.addPreference(preference)
+            }
+
+            val dark = PreferenceCategory(context).apply {
+                title = "Dark colors"
+                screen.addPreference(this)
+            }
+
+            darkColorPalette().forEachColorProperty { name, color ->
+                val preference = EditTextPreference(context)
+                preference.key = name + false
+                preference.title = name
+                // set the default value to be the default for ColorPalette
+                preference.setDefaultValue(Integer.toHexString(color.toArgb()))
+                preference.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
+                dark.addPreference(preference)
             }
             preferenceScreen = screen
         }
@@ -490,7 +521,7 @@
 private fun ColorPalette.forEachColorProperty(action: (name: String, color: Color) -> Unit) {
     ColorPalette::class.memberProperties.forEach { property ->
         val name = property.name
-        val color = property.get(this) as Color
+        val color = property.get(this) as? Color ?: return@forEach
         action(name, color)
     }
-}
\ No newline at end of file
+}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
index 5fd2c06..109a5e4 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
@@ -20,7 +20,6 @@
 import androidx.compose.unaryPlus
 import androidx.ui.core.Text
 import androidx.ui.core.dp
-import androidx.ui.graphics.Color
 import androidx.ui.layout.Column
 import androidx.ui.layout.EdgeInsets
 import androidx.ui.layout.Padding
@@ -30,7 +29,6 @@
 import androidx.ui.material.samples.RadioButtonSample
 import androidx.ui.material.samples.SwitchSample
 import androidx.ui.material.samples.TriStateCheckboxSample
-import androidx.ui.material.surface.Surface
 
 class SelectionControlsActivity : MaterialDemoActivity() {
 
@@ -39,29 +37,27 @@
         val headerStyle = (+MaterialTheme.typography()).h6
         val padding = EdgeInsets(10.dp)
 
-        Surface(color = Color.White) {
-            Padding(padding = padding) {
-                Column {
-                    Text(text = "Checkbox", style = headerStyle)
-                    Padding(padding = padding) {
-                        TriStateCheckboxSample()
-                    }
-                    Text(text = "Switch", style = headerStyle)
-                    Padding(padding = padding) {
-                        SwitchSample()
-                    }
-                    Text(text = "RadioButton", style = headerStyle)
-                    Padding(padding = padding) {
-                        RadioButtonSample()
-                    }
-                    Text(text = "Radio group :: Default usage", style = headerStyle)
-                    Padding(padding = padding) {
-                        DefaultRadioGroupSample()
-                    }
-                    Text(text = "Radio group :: Custom usage", style = headerStyle)
-                    Padding(padding = padding) {
-                        CustomRadioGroupSample()
-                    }
+        Padding(padding = padding) {
+            Column {
+                Text(text = "Checkbox", style = headerStyle)
+                Padding(padding = padding) {
+                    TriStateCheckboxSample()
+                }
+                Text(text = "Switch", style = headerStyle)
+                Padding(padding = padding) {
+                    SwitchSample()
+                }
+                Text(text = "RadioButton", style = headerStyle)
+                Padding(padding = padding) {
+                    RadioButtonSample()
+                }
+                Text(text = "Radio group :: Default usage", style = headerStyle)
+                Padding(padding = padding) {
+                    DefaultRadioGroupSample()
+                }
+                Text(text = "Radio group :: Custom usage", style = headerStyle)
+                Padding(padding = padding) {
+                    CustomRadioGroupSample()
                 }
             }
         }
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SliderActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SliderActivity.kt
index 003923b..00327259 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SliderActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SliderActivity.kt
@@ -18,25 +18,21 @@
 
 import androidx.compose.Composable
 import androidx.ui.core.dp
-import androidx.ui.graphics.Color
 import androidx.ui.layout.Column
 import androidx.ui.layout.EdgeInsets
 import androidx.ui.layout.Padding
 import androidx.ui.material.samples.SliderSample
 import androidx.ui.material.samples.StepsSliderSample
-import androidx.ui.material.surface.Surface
 
 class SliderActivity : MaterialDemoActivity() {
     @Composable
     override fun materialContent() {
         val padding = EdgeInsets(10.dp)
 
-        Surface(color = Color.White) {
-            Padding(padding = padding) {
-                Column {
-                    SliderSample()
-                    StepsSliderSample()
-                }
+        Padding(padding = padding) {
+            Column {
+                SliderSample()
+                StepsSliderSample()
             }
         }
     }
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
index ff10252..4752bd1 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
@@ -21,9 +21,9 @@
 import androidx.ui.core.em
 import androidx.ui.core.sp
 import androidx.ui.graphics.Color
-import androidx.ui.material.ColorPalette
 import androidx.ui.material.MaterialTheme
 import androidx.ui.material.Typography
+import androidx.ui.material.lightColorPalette
 import androidx.ui.text.TextStyle
 import androidx.ui.text.font.FontFamily
 import androidx.ui.text.font.FontWeight
@@ -37,7 +37,7 @@
 
 @Composable
 fun RallyTheme(children: @Composable() () -> Unit) {
-    val colors = ColorPalette(
+    val colors = lightColorPalette(
         primary = rallyGreen,
         surface = Color(0xFF26282F),
         onSurface = Color.White,
@@ -91,7 +91,7 @@
 
 @Composable
 fun RallyDialogThemeOverlay(children: @Composable() () -> Unit) {
-    val dialogColors = ColorPalette(
+    val dialogColors = lightColorPalette(
         primary = Color.White,
         surface = Color(0xFF1E1E1E),
         onSurface = Color.White
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
index 2a3412f..0b66644 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
@@ -22,11 +22,13 @@
 import androidx.ui.core.Text
 import androidx.ui.core.sp
 import androidx.ui.foundation.ColoredRect
+import androidx.ui.foundation.isSystemInDarkTheme
 import androidx.ui.graphics.Color
-import androidx.ui.material.ColorPalette
 import androidx.ui.material.FloatingActionButton
 import androidx.ui.material.MaterialTheme
 import androidx.ui.material.Typography
+import androidx.ui.material.darkColorPalette
+import androidx.ui.material.lightColorPalette
 import androidx.ui.text.TextStyle
 import androidx.ui.text.font.FontFamily
 import androidx.ui.text.font.FontWeight
@@ -34,12 +36,16 @@
 @Sampled
 @Composable
 fun MaterialThemeSample() {
-    val colors = ColorPalette(
-        primary = Color(0xFF1EB980),
-        surface = Color(0xFF26282F),
-        onSurface = Color.White
+    val lightColors = lightColorPalette(
+        primary = Color(0xFF1EB980)
     )
 
+    val darkColors = darkColorPalette(
+        primary = Color(0xFF66ffc7)
+    )
+
+    val colors = if (+isSystemInDarkTheme()) darkColors else lightColors
+
     val typography = Typography(
         h1 = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
             fontWeight = FontWeight.W100,
@@ -47,11 +53,11 @@
         button = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
             fontWeight = FontWeight.W600,
             fontSize = 14.sp)
-
     )
 
     MaterialTheme(colors = colors, typography = typography) {
-        FloatingActionButton("FAB with text style and color from theme", onClick = {})
+        val currentTheme = if ((+MaterialTheme.colors()).isLight) "light" else "dark"
+        FloatingActionButton("FAB with text style and color from $currentTheme theme", onClick = {})
     }
 }
 
@@ -65,5 +71,6 @@
 @Sampled
 @Composable
 fun ThemeTextStyleSample() {
-    Text(text = "H4 styled text", style = (+MaterialTheme.typography()).h4)
+    val typography = +MaterialTheme.typography()
+    Text(text = "H4 styled text", style = typography.h4)
 }
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt
index b973eea..abd4b27 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt
@@ -36,7 +36,7 @@
 
     @Test
     fun textColorForBackgroundUsesCorrectValues() {
-        val colors = ColorPalette(
+        val colors = lightColorPalette(
             primary = Color(0),
             onPrimary = Color(1),
             secondary = Color(2),
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Color.kt b/ui/ui-material/src/main/java/androidx/ui/material/Color.kt
index f3fa80c..ee7eb9d 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Color.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Color.kt
@@ -27,6 +27,9 @@
 /**
  * Collection of colors in the [Material color specification]
  * [https://material.io/design/color/the-color-system.html#color-theme-creation].
+ *
+ * To create a light set of colors, use [lightColorPalette]
+ * To create a dark set of colors, use [darkColorPalette]
  */
 interface ColorPalette {
     /**
@@ -86,13 +89,22 @@
      * Color used for text and icons displayed on top of the error color.
      */
     val onError: Color
+    /**
+     * Whether this ColorPalette is considered as a 'light' or 'dark' set of colors. This affects
+     * default behavior for some components: for example, in a light theme a [TopAppBar] will use
+     * [primary] by default for its background color, when in a dark theme it will use [surface].
+     */
+    val isLight: Boolean
 }
 
 /**
  * Creates a complete color definition for the [Material color specification]
- * [https://material.io/design/color/the-color-system.html#color-theme-creation].
+ * [https://material.io/design/color/the-color-system.html#color-theme-creation] using the default
+ * light theme values.
+ *
+ * @see darkColorPalette
  */
-fun ColorPalette(
+fun lightColorPalette(
     primary: Color = Color(0xFF6200EE),
     primaryVariant: Color = Color(0xFF3700B3),
     secondary: Color = Color(0xFF03DAC6),
@@ -117,7 +129,45 @@
     onSecondary,
     onBackground,
     onSurface,
-    onError
+    onError,
+    true
+)
+
+/**
+ * Creates a complete color definition for the [Material color specification]
+ * [https://material.io/design/color/the-color-system.html#color-theme-creation] using the default
+ * dark theme values.
+ *
+ * @see lightColorPalette
+ */
+fun darkColorPalette(
+    primary: Color = Color(0xFFBB86FC),
+    primaryVariant: Color = Color(0xFF3700B3),
+    secondary: Color = Color(0xFF03DAC6),
+    background: Color = Color(0xFF121212),
+    surface: Color = Color(0xFF121212),
+    error: Color = Color(0xFFCF6679),
+    onPrimary: Color = Color.Black,
+    onSecondary: Color = Color.Black,
+    onBackground: Color = Color.White,
+    onSurface: Color = Color.White,
+    onError: Color = Color.Black
+): ColorPalette = ObservableColorPalette(
+    primary,
+    primaryVariant,
+    secondary,
+    // Secondary and secondary variant are the same in dark mode, as contrast should be
+    // higher so there is no need for the variant.
+    secondary,
+    background,
+    surface,
+    error,
+    onPrimary,
+    onSecondary,
+    onBackground,
+    onSurface,
+    onError,
+    false
 )
 
 /**
@@ -146,7 +196,8 @@
     onSecondary: Color,
     onBackground: Color,
     onSurface: Color,
-    onError: Color
+    onError: Color,
+    isLight: Boolean
 ) : ColorPalette {
 
     constructor(colorPalette: ColorPalette) : this(
@@ -161,7 +212,8 @@
         onSecondary = colorPalette.onSecondary,
         onBackground = colorPalette.onBackground,
         onSurface = colorPalette.onSurface,
-        onError = colorPalette.onError
+        onError = colorPalette.onError,
+        isLight = colorPalette.isLight
     )
 
     override var primary by ObservableColor(primary)
@@ -176,6 +228,7 @@
     override var onBackground by ObservableColor(onBackground)
     override var onSurface by ObservableColor(onSurface)
     override var onError by ObservableColor(onError)
+    override var isLight by ObservableBoolean(isLight)
 }
 
 @Model
@@ -187,6 +240,15 @@
     }
 }
 
+@Model
+private class ObservableBoolean(var boolean: Boolean) {
+    operator fun getValue(thisObj: Any?, property: KProperty<*>) = boolean
+
+    operator fun setValue(thisObj: Any?, property: KProperty<*>, next: Boolean) {
+        if (boolean != next) boolean = next
+    }
+}
+
 /**
  * Updates the internal values of the given [ObservableColorPalette] with values from the [other]
  * [ColorPalette].
@@ -204,6 +266,7 @@
     onBackground = other.onBackground
     onSurface = other.onSurface
     onError = other.onError
+    isLight = other.isLight
     return this
 }
 
@@ -215,9 +278,7 @@
 internal fun ProvideColorPalette(colorPalette: ColorPalette, children: @Composable() () -> Unit) {
     val palette = when (colorPalette) {
         is ObservableColorPalette -> {
-            (+memo<ObservableColorPalette> {
-                ObservableColorPalette(colorPalette)
-            }).updateColorsFrom(colorPalette)
+            (+memo { ObservableColorPalette(colorPalette) }).updateColorsFrom(colorPalette)
         }
         else -> colorPalette
     }
@@ -229,4 +290,4 @@
  *
  * To retrieve the current value of this ambient, use [MaterialTheme.colors].
  */
-internal val ColorAmbient = Ambient.of { ColorPalette() }
+internal val ColorAmbient = Ambient.of { lightColorPalette() }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
index eeccfeb..0bc689d 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
@@ -41,7 +41,7 @@
  */
 @Composable
 fun MaterialTheme(
-    colors: ColorPalette = ColorPalette(),
+    colors: ColorPalette = lightColorPalette(),
     typography: Typography = Typography(),
     children: @Composable() () -> Unit
 ) {
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/SemanticsTreeNodeImpl.kt b/ui/ui-platform/src/main/java/androidx/ui/core/SemanticsTreeNodeImpl.kt
index 66344f6..a8f9765 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/SemanticsTreeNodeImpl.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/SemanticsTreeNodeImpl.kt
@@ -81,7 +81,7 @@
         )
         parent?.addChild(wrapper)
         nodes.add(wrapper)
-        currentParent = parent
+        currentParent = wrapper
     }
 
     currentNode.visitChildren {
diff --git a/ui/ui-test/api/0.1.0-dev03.txt b/ui/ui-test/api/0.1.0-dev03.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/0.1.0-dev03.txt
+++ b/ui/ui-test/api/0.1.0-dev03.txt
@@ -4,6 +4,7 @@
   public final class ActionsKt {
     ctor public ActionsKt();
     method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+    method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
     method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
     method public static boolean waitForIdleCompose();
   }
@@ -140,6 +141,20 @@
     method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
   }
 
+  public final class GestureScope {
+  }
+
+  public final class GestureScopeKt {
+    ctor public GestureScopeKt();
+    method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+    method public static void sendClick(androidx.ui.test.GestureScope);
+    method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+    method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+    method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+    method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+    method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+  }
+
   public final class GoldenSemanticsKt {
     ctor public GoldenSemanticsKt();
     method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/current.txt b/ui/ui-test/api/current.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/current.txt
+++ b/ui/ui-test/api/current.txt
@@ -4,6 +4,7 @@
   public final class ActionsKt {
     ctor public ActionsKt();
     method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+    method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
     method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
     method public static boolean waitForIdleCompose();
   }
@@ -140,6 +141,20 @@
     method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
   }
 
+  public final class GestureScope {
+  }
+
+  public final class GestureScopeKt {
+    ctor public GestureScopeKt();
+    method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+    method public static void sendClick(androidx.ui.test.GestureScope);
+    method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+    method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+    method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+    method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+    method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+  }
+
   public final class GoldenSemanticsKt {
     ctor public GoldenSemanticsKt();
     method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-test/api/public_plus_experimental_0.1.0-dev03.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/public_plus_experimental_0.1.0-dev03.txt
+++ b/ui/ui-test/api/public_plus_experimental_0.1.0-dev03.txt
@@ -4,6 +4,7 @@
   public final class ActionsKt {
     ctor public ActionsKt();
     method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+    method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
     method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
     method public static boolean waitForIdleCompose();
   }
@@ -140,6 +141,20 @@
     method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
   }
 
+  public final class GestureScope {
+  }
+
+  public final class GestureScopeKt {
+    ctor public GestureScopeKt();
+    method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+    method public static void sendClick(androidx.ui.test.GestureScope);
+    method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+    method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+    method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+    method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+    method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+  }
+
   public final class GoldenSemanticsKt {
     ctor public GoldenSemanticsKt();
     method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/public_plus_experimental_current.txt b/ui/ui-test/api/public_plus_experimental_current.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/ui/ui-test/api/public_plus_experimental_current.txt
@@ -4,6 +4,7 @@
   public final class ActionsKt {
     ctor public ActionsKt();
     method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+    method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
     method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
     method public static boolean waitForIdleCompose();
   }
@@ -140,6 +141,20 @@
     method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
   }
 
+  public final class GestureScope {
+  }
+
+  public final class GestureScopeKt {
+    ctor public GestureScopeKt();
+    method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+    method public static void sendClick(androidx.ui.test.GestureScope);
+    method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+    method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+    method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+    method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+    method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+  }
+
   public final class GoldenSemanticsKt {
     ctor public GoldenSemanticsKt();
     method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/restricted_0.1.0-dev03.txt b/ui/ui-test/api/restricted_0.1.0-dev03.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/restricted_0.1.0-dev03.txt
+++ b/ui/ui-test/api/restricted_0.1.0-dev03.txt
@@ -4,6 +4,7 @@
   public final class ActionsKt {
     ctor public ActionsKt();
     method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+    method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
     method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
     method public static boolean waitForIdleCompose();
   }
@@ -140,6 +141,20 @@
     method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
   }
 
+  public final class GestureScope {
+  }
+
+  public final class GestureScopeKt {
+    ctor public GestureScopeKt();
+    method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+    method public static void sendClick(androidx.ui.test.GestureScope);
+    method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+    method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+    method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+    method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+    method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+  }
+
   public final class GoldenSemanticsKt {
     ctor public GoldenSemanticsKt();
     method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/restricted_current.txt b/ui/ui-test/api/restricted_current.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/restricted_current.txt
+++ b/ui/ui-test/api/restricted_current.txt
@@ -4,6 +4,7 @@
   public final class ActionsKt {
     ctor public ActionsKt();
     method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+    method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
     method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
     method public static boolean waitForIdleCompose();
   }
@@ -140,6 +141,20 @@
     method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
   }
 
+  public final class GestureScope {
+  }
+
+  public final class GestureScopeKt {
+    ctor public GestureScopeKt();
+    method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+    method public static void sendClick(androidx.ui.test.GestureScope);
+    method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+    method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+    method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+    method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+    method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+  }
+
   public final class GoldenSemanticsKt {
     ctor public GoldenSemanticsKt();
     method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/build.gradle b/ui/ui-test/build.gradle
index 78872d5..b4ccacaf 100644
--- a/ui/ui-test/build.gradle
+++ b/ui/ui-test/build.gradle
@@ -48,7 +48,7 @@
     testImplementation(TRUTH)
 
     androidTestImplementation(TRUTH)
-
+    androidTestImplementation project(":ui:ui-core")
     androidTestImplementation project(':ui:ui-material')
 }
 
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/PointerInputRecorder.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/PointerInputRecorder.kt
new file mode 100644
index 0000000..c274abb
--- /dev/null
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/PointerInputRecorder.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.test
+
+import androidx.ui.core.IntPxSize
+import androidx.ui.core.PointerEventPass
+import androidx.ui.core.PointerInputChange
+import androidx.ui.core.PointerInputData
+import com.google.common.truth.Truth
+
+class PointerInputRecorder {
+
+    data class DataPoint(val id: Int, val data: PointerInputData) {
+        val timestamp get() = data.uptime
+        val position get() = data.position
+        val down get() = data.down
+    }
+
+    private val _events = mutableListOf<DataPoint>()
+    val events get() = _events as List<DataPoint>
+
+    fun onPointerInput(
+        changes: List<PointerInputChange>,
+        pass: PointerEventPass,
+        @Suppress("UNUSED_PARAMETER") bounds: IntPxSize
+    ): List<PointerInputChange> {
+        if (pass == PointerEventPass.InitialDown) {
+            changes.forEach {
+                _events.add(DataPoint(it.id, it.current))
+            }
+        }
+        return changes
+    }
+
+    fun assertTimestampsAreIncreasing() {
+        events.reduce { prev, curr ->
+            Truth.assertThat(curr.timestamp).isAtLeast(prev.timestamp)
+            curr
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/SendSwipeTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/SendSwipeTest.kt
new file mode 100644
index 0000000..f16eb24
--- /dev/null
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/SendSwipeTest.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.test
+
+import androidx.compose.Composable
+import androidx.test.filters.MediumTest
+import androidx.ui.core.Alignment
+import androidx.ui.core.PointerInputWrapper
+import androidx.ui.core.TestTag
+import androidx.ui.core.dp
+import androidx.ui.foundation.shape.DrawShape
+import androidx.ui.foundation.shape.RectangleShape
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Align
+import androidx.ui.layout.Container
+import androidx.ui.semantics.Semantics
+import com.google.common.collect.Ordering
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@MediumTest
+@RunWith(JUnit4::class)
+class SendSwipeTest {
+
+    private val tag = "widget"
+
+    @get:Rule
+    val composeTestRule = createComposeRule(disableTransitions = true)
+
+    private lateinit var recorder: PointerInputRecorder
+
+    @Before
+    fun setup() {
+        recorder = PointerInputRecorder()
+    }
+
+    @Composable
+    fun Ui(alignment: Alignment) {
+        PointerInputWrapper(pointerInputHandler = recorder::onPointerInput) {
+            Align(alignment = alignment) {
+                TestTag(tag) {
+                    Semantics {
+                        Container(width = 100.dp, height = 100.dp) {
+                            DrawShape(RectangleShape, Color.Yellow)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun swipeUp() {
+        composeTestRule.setContent { Ui(Alignment.TopLeft) }
+        findByTag(tag).doGesture { sendSwipeUp() }
+        recorder.run {
+            assertTimestampsAreIncreasing()
+            assertOnlyLastEventIsUp()
+            assertSwipeIsUp()
+        }
+    }
+
+    @Test
+    fun swipeDown() {
+        composeTestRule.setContent { Ui(Alignment.TopRight) }
+        findByTag(tag).doGesture { sendSwipeDown() }
+        recorder.run {
+            assertTimestampsAreIncreasing()
+            assertOnlyLastEventIsUp()
+            assertSwipeIsDown()
+        }
+    }
+
+    @Test
+    fun swipeLeft() {
+        composeTestRule.setContent { Ui(Alignment.BottomRight) }
+        findByTag(tag).doGesture { sendSwipeLeft() }
+        recorder.run {
+            assertTimestampsAreIncreasing()
+            assertOnlyLastEventIsUp()
+            assertSwipeIsLeft()
+        }
+    }
+
+    @Test
+    fun swipeRight() {
+        composeTestRule.setContent { Ui(Alignment.BottomLeft) }
+        findByTag(tag).doGesture { sendSwipeRight() }
+        recorder.run {
+            assertTimestampsAreIncreasing()
+            assertOnlyLastEventIsUp()
+            assertSwipeIsRight()
+        }
+    }
+}
+
+private fun PointerInputRecorder.assertOnlyLastEventIsUp() {
+    assertThat(events.last().down).isFalse()
+    assertThat(events.filter { !it.down }.size).isEqualTo(1)
+}
+
+private fun PointerInputRecorder.assertSwipeIsUp() {
+    // Must have at least two events to have a direction
+    assertThat(events.size).isAtLeast(2)
+    // Last event must be above first event
+    assertThat(events.last().position!!.y).isLessThan(events.first().position!!.y)
+    // All events in between only move up
+    events.map { it.position!!.x.value }.assertSame(tolerance = 0.001f)
+    events.map { it.position!!.y }.assertDecreasing()
+}
+
+private fun PointerInputRecorder.assertSwipeIsDown() {
+    // Must have at least two events to have a direction
+    assertThat(events.size).isAtLeast(2)
+    // Last event must be below first event
+    assertThat(events.last().position!!.y).isGreaterThan(events.first().position!!.y)
+    // All events in between only move down
+    events.map { it.position!!.x.value }.assertSame(tolerance = 0.001f)
+    events.map { it.position!!.y }.assertIncreasing()
+}
+
+private fun PointerInputRecorder.assertSwipeIsLeft() {
+    // Must have at least two events to have a direction
+    assertThat(events.size).isAtLeast(2)
+    // Last event must be to the left of first event
+    assertThat(events.last().position!!.x).isLessThan(events.first().position!!.x)
+    // All events in between only move to the left
+    events.map { it.position!!.x }.assertDecreasing()
+    events.map { it.position!!.y.value }.assertSame(tolerance = 0.001f)
+}
+
+private fun PointerInputRecorder.assertSwipeIsRight() {
+    // Must have at least two events to have a direction
+    assertThat(events.size).isAtLeast(2)
+    // Last event must be to the right of first event
+    assertThat(events.last().position!!.x).isGreaterThan(events.first().position!!.x)
+    // All events in between only move to the right
+    events.map { it.position!!.x }.assertIncreasing()
+    events.map { it.position!!.y.value }.assertSame(tolerance = 0.001f)
+}
+
+private fun List<Float>.assertSame(tolerance: Float = 0f) {
+    if (size <= 1) {
+        return
+    }
+    val baseValue = first()
+    assertThat(min()).isWithin(tolerance).of(baseValue)
+    assertThat(max()).isWithin(tolerance).of(baseValue)
+}
+
+private fun <E : Comparable<E>> List<E>.assertIncreasing() {
+    assertThat(this).isInOrder(Ordering.natural<E>())
+}
+
+private fun <E : Comparable<E>> List<E>.assertDecreasing() {
+    assertThat(this).isInOrder(Ordering.natural<E>().reverse<E>())
+}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt b/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt
index 86e74da..109498b 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt
@@ -27,18 +27,17 @@
  * Performs a click action on the given component.
  */
 fun SemanticsNodeInteraction.doClick(): SemanticsNodeInteraction {
-    // TODO(b/129400818): uncomment this after Merge Semantics is merged
-    // assertHasClickAction()
-
-    // TODO(catalintudor): get real coordinates after Semantics API is ready (b/125702443)
-    val globalRect = semanticsTreeNode.globalRect
-        ?: throw AssertionError("Semantic Node has no child layout to perform click on!")
-    val x = globalRect.left + 1f
-    val y = globalRect.top + 1f
-
-    semanticsTreeInteraction.sendClick(x, y)
-
-    return this
+    // TODO(jellefresen): Replace with semantics action when semantics merging is done
+    // The problem we currently have is that the click action might be defined on a different
+    // semantics node than we're interacting with now, even though it is "semantically" the same.
+    // E.g., findByText(buttonText) finds the Text's semantics node, but the click action is
+    // defined on the wrapping Button's semantics node.
+    // Since in general the intended click action can be on a wrapping node or a child node, we
+    // can't just forward to the correct node, as we don't know if we should search up or down the
+    // tree.
+    return doGesture {
+        sendClick()
+    }
 }
 
 /**
@@ -85,4 +84,21 @@
     return this
 }
 
+/**
+ * Executes the gestures specified in the given block.
+ *
+ * Example usage:
+ * findByTag("myWidget")
+ *    .doGesture {
+ *        sendSwipeUp()
+ *    }
+ */
+fun SemanticsNodeInteraction.doGesture(
+    block: GestureScope.() -> Unit
+): SemanticsNodeInteraction {
+    val scope = GestureScope(this)
+    scope.block()
+    return this
+}
+
 fun waitForIdleCompose(): Boolean = semanticsTreeInteractionFactory({ true }).waitForIdleCompose()
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt b/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt
new file mode 100644
index 0000000..3d5daf2
--- /dev/null
+++ b/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.test
+
+import androidx.ui.core.Duration
+import androidx.ui.core.SemanticsTreeNode
+import androidx.ui.core.milliseconds
+
+/**
+ * An object that has an associated component in which one can inject gestures. The gestures can
+ * be injected by calling methods defined on [GestureScope], such as [sendSwipeUp]. The associated
+ * component is the [SemanticsTreeNode] found by one of the finder methods such as [findByTag].
+ *
+ * Example usage:
+ * findByTag("myWidget")
+ *    .doGesture {
+ *        sendSwipeUp()
+ *    }
+ */
+class GestureScope internal constructor(
+    internal val semanticsNodeInteraction: SemanticsNodeInteraction
+) {
+    internal inline val semanticsTreeNode
+        get() = semanticsNodeInteraction.semanticsTreeNode
+    internal inline val semanticsTreeInteraction
+        get() = semanticsNodeInteraction.semanticsTreeInteraction
+}
+
+/**
+ * The distance of a swipe's start position from the node's edge, in terms of the node's length.
+ * We do not start the swipe exactly on the node's edge, but somewhat more inward, since swiping
+ * from the exact edge may behave in an unexpected way (e.g. may open a navigation drawer).
+ */
+private const val edgeFuzzFactor = 0.083f
+
+/**
+ * Performs a click gesture on the given coordinate on the associated component. The coordinate
+ * ([x], [y]) is in the component's local coordinate system.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendClick(x: Float, y: Float) {
+    val globalRect = semanticsTreeNode.globalRect
+        ?: throw AssertionError("Semantic Node has no child layout to perform click on!")
+    val xOffset = globalRect.left
+    val yOffset = globalRect.top
+
+    semanticsTreeInteraction.sendInput {
+        it.sendClick(x + xOffset, y + yOffset)
+    }
+}
+
+/**
+ * Performs a click gesture on the associated component. The click is done in the middle of the
+ * component's bounds.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendClick() {
+    val globalRect = semanticsTreeNode.globalRect
+        ?: throw AssertionError("Semantic Node has no child layout to perform click on!")
+    val x = globalRect.width / 2
+    val y = globalRect.height / 2
+
+    sendClick(x, y)
+}
+
+/**
+ * Performs the swipe gesture on the associated component. The MotionEvents are linearly
+ * interpolated between ([x0], [y0]) and ([x1], [y1]). The coordinates are in the component's local
+ * coordinate system, i.e. (0, 0) is the top left corner of the component. The default duration is
+ * 200 milliseconds.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipe(
+    x0: Float,
+    y0: Float,
+    x1: Float,
+    y1: Float,
+    duration: Duration = 200.milliseconds
+) {
+    val globalRect = semanticsTreeNode.globalRect
+        ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+    val xOffset = globalRect.left
+    val yOffset = globalRect.top
+
+    semanticsTreeInteraction.sendInput {
+        it.sendSwipe(x0 + xOffset, y0 + yOffset, x1 + xOffset, y1 + yOffset, duration)
+    }
+}
+
+/**
+ * Performs a swipe up gesture on the associated component. The gesture starts slightly above the
+ * bottom of the component and ends at the top.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipeUp() {
+    val globalRect = semanticsTreeNode.globalRect
+        ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+    val x = globalRect.width / 2
+    val y0 = globalRect.height * (1 - edgeFuzzFactor)
+    val y1 = 0f
+
+    sendSwipe(x, y0, x, y1, 200.milliseconds)
+}
+
+/**
+ * Performs a swipe down gesture on the associated component. The gesture starts slightly below the
+ * top of the component and ends at the bottom.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipeDown() {
+    val globalRect = semanticsTreeNode.globalRect
+        ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+    val x = globalRect.width / 2
+    val y0 = globalRect.height * edgeFuzzFactor
+    val y1 = globalRect.height
+
+    sendSwipe(x, y0, x, y1, 200.milliseconds)
+}
+
+/**
+ * Performs a swipe left gesture on the associated component. The gesture starts slightly left of
+ * the right side of the component and ends at the left side.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipeLeft() {
+    val globalRect = semanticsTreeNode.globalRect
+        ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+    val x0 = globalRect.width * (1 - edgeFuzzFactor)
+    val x1 = 0f
+    val y = globalRect.height / 2
+
+    sendSwipe(x0, y, x1, y, 200.milliseconds)
+}
+
+/**
+ * Performs a swipe right gesture on the associated component. The gesture starts slightly right of
+ * the left side of the component and ends at the right side.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipeRight() {
+    val globalRect = semanticsTreeNode.globalRect
+        ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+    val x0 = globalRect.width * edgeFuzzFactor
+    val x1 = globalRect.width
+    val y = globalRect.height / 2
+
+    sendSwipe(x0, y, x1, y, 200.milliseconds)
+}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt b/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt
new file mode 100644
index 0000000..c8b061a
--- /dev/null
+++ b/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.test
+
+import androidx.ui.core.Duration
+
+internal interface InputDispatcher {
+    /**
+     * Sends a click event at coordinate ([x], [y]). There will be 10ms in between the down and
+     * the up event. This method blocks until all input events have been dispatched.
+     */
+    fun sendClick(x: Float, y: Float)
+
+    /**
+     * Sends a swipe gesture from ([x0], [y0]) to ([x1], [y1]) with the given [duration]. This
+     * method blocks until all input events have been dispatched.
+     */
+    fun sendSwipe(x0: Float, y0: Float, x1: Float, y1: Float, duration: Duration)
+}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/SemanticsTreeInteraction.kt b/ui/ui-test/src/main/java/androidx/ui/test/SemanticsTreeInteraction.kt
index 94a780f..d51eb8a 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/SemanticsTreeInteraction.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/SemanticsTreeInteraction.kt
@@ -26,6 +26,7 @@
  * extension functions. This class is expected to have Android and host side specific
  * implementations.
  */
+// TODO(jellefresen): Convert to interface, no need for an abstract class
 internal abstract class SemanticsTreeInteraction {
 
     internal abstract fun findAllMatching(): List<SemanticsNodeInteraction>
@@ -34,7 +35,7 @@
 
     internal abstract fun performAction(action: (SemanticsTreeProvider) -> Unit)
 
-    internal abstract fun sendClick(x: Float, y: Float)
+    internal abstract fun sendInput(action: (InputDispatcher) -> Unit)
 
     internal abstract fun contains(semanticsConfiguration: SemanticsConfiguration): Boolean
 
@@ -58,4 +59,4 @@
  * just wait for longer timeouts as the tests might still have a value.
  */
 // TODO(pavlis): Turn this on by default for all our Compose tests
-internal var throwOnRecomposeTimeout = false
\ No newline at end of file
+internal var throwOnRecomposeTimeout = false
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt
new file mode 100644
index 0000000..d7ab848
--- /dev/null
+++ b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.test.android
+
+import android.os.Handler
+import android.os.Looper
+import android.os.SystemClock
+import android.view.MotionEvent
+import androidx.ui.core.Duration
+import androidx.ui.core.SemanticsTreeProvider
+import androidx.ui.core.inMilliseconds
+import androidx.ui.core.milliseconds
+import androidx.ui.lerp
+import androidx.ui.test.InputDispatcher
+import java.util.concurrent.CountDownLatch
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+internal class AndroidInputDispatcher(
+    private val treeProvider: SemanticsTreeProvider
+) : InputDispatcher {
+    /**
+     * The minimum time between two successive injected MotionEvents. Ideally, the value should
+     * reflect a realistic pointer input sample rate, but that depends on too many factors. Instead,
+     * the value is chosen comfortably below the targeted frame rate (60 fps, equating to a 16ms
+     * period).
+     */
+    private val eventPeriod = 10.milliseconds.inMilliseconds()
+
+    private val handler = Handler(Looper.getMainLooper())
+
+    override fun sendClick(x: Float, y: Float) {
+        val downTime = SystemClock.uptimeMillis()
+        treeProvider.sendMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, x, y)
+        treeProvider.sendMotionEvent(downTime, downTime + eventPeriod, MotionEvent.ACTION_UP, x, y)
+    }
+
+    override fun sendSwipe(x0: Float, y0: Float, x1: Float, y1: Float, duration: Duration) {
+        var step = 0
+        val steps = min(1, (duration.inMilliseconds() / eventPeriod.toFloat()).roundToInt())
+        val downTime = SystemClock.uptimeMillis()
+        val upTime = downTime + duration.inMilliseconds()
+
+        treeProvider.sendMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, x0, y0)
+        while (step++ < steps) {
+            val progress = step / steps.toFloat()
+            val t = lerp(downTime, upTime, progress)
+            val x = lerp(x0, x1, progress)
+            val y = lerp(y0, y1, progress)
+            treeProvider.sendMotionEvent(downTime, t, MotionEvent.ACTION_MOVE, x, y)
+        }
+        treeProvider.sendMotionEvent(downTime, upTime, MotionEvent.ACTION_UP, x1, y1)
+    }
+
+    /**
+     * Sends an event with the given parameters. Method blocks depending on [waitUntilEventTime].
+     * @param waitUntilEventTime If `true`, blocks until [eventTime]
+     */
+    private fun SemanticsTreeProvider.sendMotionEvent(
+        downTime: Long,
+        eventTime: Long,
+        action: Int,
+        x: Float,
+        y: Float,
+        waitUntilEventTime: Boolean = true
+    ) {
+        if (waitUntilEventTime) {
+            val currTime = SystemClock.uptimeMillis()
+            if (currTime < eventTime) {
+                SystemClock.sleep(eventTime - currTime)
+            }
+        }
+        sendAndRecycleEvent(MotionEvent.obtain(downTime, eventTime, action, x, y, 0))
+    }
+
+    /**
+     * Sends the [event] to the [SemanticsTreeProvider] and [recycles][MotionEvent.recycle] it
+     * regardless of the result. This method blocks until the event is sent.
+     */
+    private fun SemanticsTreeProvider.sendAndRecycleEvent(event: MotionEvent) {
+        val latch = CountDownLatch(1)
+        handler.post {
+            try {
+                sendEvent(event)
+            } finally {
+                event.recycle()
+                latch.countDown()
+            }
+        }
+        latch.await()
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidSemanticsTreeInteraction.kt b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidSemanticsTreeInteraction.kt
index 59feac9..370ee91 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidSemanticsTreeInteraction.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidSemanticsTreeInteraction.kt
@@ -22,7 +22,6 @@
 import android.content.Context
 import android.os.Handler
 import android.os.Looper
-import android.os.SystemClock
 import android.view.Choreographer
 import android.view.MotionEvent
 import android.view.View
@@ -38,6 +37,7 @@
 import androidx.ui.core.px
 import androidx.ui.core.semantics.SemanticsConfiguration
 import androidx.ui.engine.geometry.Rect
+import androidx.ui.test.InputDispatcher
 import androidx.ui.test.SemanticsNodeInteraction
 import androidx.ui.test.SemanticsTreeInteraction
 import java.util.concurrent.CountDownLatch
@@ -111,6 +111,11 @@
         hadPendingChangesAfterLastAction = waitForIdleCompose()
     }
 
+    override fun sendInput(action: (InputDispatcher) -> Unit) {
+        action(AndroidInputDispatcher(findActivityAndTreeProvider().treeProvider))
+        hadPendingChangesAfterLastAction = waitForIdleCompose()
+    }
+
     /**
      * Waits for Compose runtime to be idle - meaning it has no pending changes.
      *
@@ -153,25 +158,6 @@
         })
     }
 
-    override fun sendClick(x: Float, y: Float) {
-        performAction { treeProvider ->
-            val downTime = SystemClock.uptimeMillis()
-            val eventDown = MotionEvent.obtain(
-                downTime, downTime,
-                MotionEvent.ACTION_DOWN, x, y, 0
-            )
-            treeProvider.sendEvent(eventDown)
-            eventDown.recycle()
-
-            val eventUp = MotionEvent.obtain(
-                downTime, downTime + 10,
-                MotionEvent.ACTION_UP, x, y, 0
-            )
-            treeProvider.sendEvent(eventUp)
-            eventUp.recycle()
-        }
-    }
-
     override fun contains(semanticsConfiguration: SemanticsConfiguration): Boolean {
         waitForIdleCompose()
 
@@ -281,4 +267,4 @@
         val context: Context,
         val treeProvider: SemanticsTreeProvider
     )
-}
\ No newline at end of file
+}
diff --git a/ui/ui-test/src/test/java/androidx/ui/test/helpers/FakeSemanticsTreeInteraction.kt b/ui/ui-test/src/test/java/androidx/ui/test/helpers/FakeSemanticsTreeInteraction.kt
index d580c4c..6525ece 100644
--- a/ui/ui-test/src/test/java/androidx/ui/test/helpers/FakeSemanticsTreeInteraction.kt
+++ b/ui/ui-test/src/test/java/androidx/ui/test/helpers/FakeSemanticsTreeInteraction.kt
@@ -20,6 +20,7 @@
 import androidx.ui.core.SemanticsTreeProvider
 import androidx.ui.core.semantics.SemanticsConfiguration
 import androidx.ui.engine.geometry.Rect
+import androidx.ui.test.InputDispatcher
 import androidx.ui.test.SemanticsNodeInteraction
 import androidx.ui.test.SemanticsTreeInteraction
 import androidx.ui.test.SemanticsTreeNodeStub
@@ -69,7 +70,7 @@
         TODO("replace with host side interaction")
     }
 
-    override fun sendClick(x: Float, y: Float) {
+    override fun sendInput(action: (InputDispatcher) -> Unit) {
         TODO("replace with host side interaction")
     }
 
diff --git a/webkit/integration-tests/testapp/README.md b/webkit/integration-tests/testapp/README.md
index 7e48107..34e18f1 100644
--- a/webkit/integration-tests/testapp/README.md
+++ b/webkit/integration-tests/testapp/README.md
@@ -10,7 +10,7 @@
 cd frameworks/support/
 
 # Optional: you can use Android Studio as your editor
-./studiow -y
+./studiow
 
 # Build the app
 ./gradlew :webkit:integration-tests:testapp:assembleDebug
diff --git a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
index a31db81..9cfd702 100644
--- a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -102,5 +102,8 @@
         <activity
             android:name=".FullscreenActivity"
             android:exported="true" />
+        <activity
+            android:name=".WebMessageListenerActivity"
+            android:exported="true" />
     </application>
 </manifest>
diff --git a/webkit/integration-tests/testapp/src/main/assets/www/web_message_listener.html b/webkit/integration-tests/testapp/src/main/assets/www/web_message_listener.html
new file mode 100644
index 0000000..11ed1b0
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/assets/www/web_message_listener.html
@@ -0,0 +1,83 @@
+<html>
+<!-- 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.
+-->
+<head>
+  <script type="text/javascript">
+    let timestamp = 0;
+    replyObject.onmessage = function(event) {
+        document.getElementById("result").innerHTML = event.data;
+    };
+    // Send a message to app, so app could get a JsReplyProxy to reply back to JavaScript.
+    replyObject.postMessage("initialization");
+
+    const channel = new MessageChannel();
+    channel.port2.onmessage = function(event) {
+        document.getElementById("port_result").innerHTML = event.data;
+    };
+    replyWithMessagePortObject.postMessage("send port", [channel.port1]);
+
+    let messageCounter = 0;
+    multipleMessagesObject.addEventListener("message", function(event) {
+        const i = parseInt(event.data);
+        if (i < 4999) {
+            multipleMessagesObject.postMessage((i + 1).toString());
+            if (i % 100 === 0) {
+                const counterInfo = (messageCounter + i)+ " messages sent.";
+                document.getElementById("multiple_messages").innerHTML = counterInfo;
+            }
+        } else {
+            let average = (performance.now() - timestamp) / 5000;
+            let result = "Average time over 5000 messages: " + average.toFixed(2) + "ms.";
+            document.getElementById("multiple_messages").innerHTML = result;
+            messageCounter += i + 1;
+            document.getElementById("multiple_button").disabled = false;
+        }
+    });
+    function startPostMessage(e) {
+        e.preventDefault();
+        document.getElementById("multiple_button").disabled = true;
+        timestamp = performance.now();
+        multipleMessagesObject.postMessage("0");
+    }
+
+    function showToast(e) {
+        e.preventDefault();
+        const message = document.getElementById("toast_message").value;
+        toastObject.postMessage(message);
+    }
+  </script>
+</head>
+<body>
+  <h1>Web content (within WebView)</h1>
+  <div>
+    <span>Message from app: </span>
+    <span id="result" style="color:red">Not received.</span>
+  </div>
+  <div>
+    <span>Message from app (via MessagePort): </span>
+    <span id="port_result" style="color:red">Not received.</span>
+  </div>
+  <div>
+    <input value="toast!" id="toast_message">
+    <button type="button" onclick="showToast(event)">Show Toast</button>
+  </div>
+  <div>
+    <button id="multiple_button" type="button" onclick="startPostMessage(event)">
+      Send 5000 messages
+    </button>
+    <div id="multiple_messages"></div>
+  </div>
+</body>
+</html>
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java
index 7c55bc6..634da07 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java
@@ -66,6 +66,9 @@
                 new MenuListView.MenuItem(
                         getResources().getString(R.string.fullscreen_activity_title),
                         new Intent(activityContext, FullscreenActivity.class)),
+                new MenuListView.MenuItem(
+                        getResources().getString(R.string.web_message_listener_activity_title),
+                        new Intent(activityContext, WebMessageListenerActivity.class)),
         };
         listView.setItems(menuItems);
     }
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java
new file mode 100644
index 0000000..6ebec6e
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java
@@ -0,0 +1,216 @@
+/*
+ * 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 com.example.androidx.webkit;
+
+import static androidx.webkit.WebViewAssetLoader.AssetsPathHandler;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.AbsoluteSizeSpan;
+import android.view.View;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.webkit.JsReplyProxy;
+import androidx.webkit.WebMessageCompat;
+import androidx.webkit.WebMessagePortCompat;
+import androidx.webkit.WebViewAssetLoader;
+import androidx.webkit.WebViewCompat;
+import androidx.webkit.WebViewFeature;
+
+import java.util.Arrays;
+
+/**
+ * An {@link Activity} to exercise WebMessageListener related functionality.
+ */
+@SuppressLint("RestrictedApi")
+public class WebMessageListenerActivity extends AppCompatActivity {
+    private TextView mTextView;
+    private final Uri mExampleUri = new Uri.Builder()
+                                            .scheme("https")
+                                            .authority("example.com")
+                                            .appendPath("androidx_webkit")
+                                            .appendPath("example")
+                                            .appendPath("assets")
+                                            .build();
+    private Button mReplyProxyButton;
+    private Button mPortButton;
+
+    private static class MyWebViewClient extends WebViewClient {
+        private final WebViewAssetLoader mAssetLoader;
+
+        MyWebViewClient(WebViewAssetLoader loader) {
+            mAssetLoader = loader;
+        }
+
+        @Override
+        @RequiresApi(21)
+        public WebResourceResponse shouldInterceptRequest(WebView view,
+                                            WebResourceRequest request) {
+            return mAssetLoader.shouldInterceptRequest(request.getUrl());
+        }
+
+        @Override
+        @SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
+        public WebResourceResponse shouldInterceptRequest(WebView view, String request) {
+            return mAssetLoader.shouldInterceptRequest(Uri.parse(request));
+        }
+    }
+
+    private static class ReplyMessageListener implements WebViewCompat.WebMessageListener {
+        private final Context mContext;
+        private JsReplyProxy mReplyProxy;
+
+        ReplyMessageListener(Context context, Button button) {
+            mContext = context;
+            button.setOnClickListener((View v) -> {
+                if (mReplyProxy == null) return;
+                mReplyProxy.postMessage("ReplyProxy button clicked.");
+            });
+        }
+
+        @Override
+        public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
+                boolean isMainFrame, JsReplyProxy replyProxy) {
+            if (message.getData().equals("initialization")) {
+                mReplyProxy = replyProxy;
+            }
+        }
+    }
+
+    private static class MessagePortMessageListener implements WebViewCompat.WebMessageListener {
+        private final Context mContext;
+        private WebMessagePortCompat mPort;
+
+        MessagePortMessageListener(Context context, Button button) {
+            mContext = context;
+            button.setOnClickListener((View v) -> {
+                if (mPort == null) return;
+                mPort.postMessage(new WebMessageCompat("Port button clicked."));
+            });
+        }
+
+        @Override
+        public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
+                boolean isMainFrame, JsReplyProxy replyProxy) {
+            if (message.getData().equals("send port")) {
+                mPort = message.getPorts()[0];
+            }
+        }
+    }
+
+    private static class ToastMessageListener implements WebViewCompat.WebMessageListener {
+        private final Context mContext;
+
+        ToastMessageListener(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
+                boolean isMainFrame, JsReplyProxy replyProxy) {
+            Toast.makeText(mContext, "Toast: " + message.getData(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private static class MultipleMessagesListener implements WebViewCompat.WebMessageListener {
+        private final Context mContext;
+        private final TextView mTextView;
+        private int mCounter = 0;
+
+        MultipleMessagesListener(Context context, TextView textView) {
+            mContext = context;
+            mTextView = textView;
+        }
+
+        @Override
+        public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
+                boolean isMainFrame, JsReplyProxy replyProxy) {
+            replyProxy.postMessage(message.getData());
+            mCounter++;
+            if (mCounter % 100 == 0) {
+                mTextView.setText(TextUtils.concat(
+                        createNativeTitle(), "\n", "" + mCounter + " messages received."));
+            }
+        }
+    }
+
+    @SuppressLint("SetJavascriptEnabled")
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_web_message_listener);
+        setTitle(R.string.web_message_listener_activity_title);
+        WebkitHelpers.appendWebViewVersionToTitle(this);
+
+        // Use WebViewAssetLoader to load html page from app's assets.
+        WebViewAssetLoader assetLoader =
+                new WebViewAssetLoader.Builder()
+                        .setDomain("example.com")
+                        .addPathHandler(mExampleUri.getPath() + "/", new AssetsPathHandler(this))
+                        .build();
+
+        mTextView = findViewById(R.id.textview);
+        mTextView.setText(createNativeTitle());
+
+        mReplyProxyButton = findViewById(R.id.button_reply_proxy);
+        mPortButton = findViewById(R.id.button_port);
+
+        WebView webView = findViewById(R.id.webview);
+        webView.setWebViewClient(new MyWebViewClient(assetLoader));
+        webView.getSettings().setJavaScriptEnabled(true);
+
+        // Add WebMessageListeners.
+        if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
+            WebViewCompat.addWebMessageListener(webView, "replyObject",
+                    Arrays.asList("https://example.com"),
+                    new ReplyMessageListener(this, mReplyProxyButton));
+            WebViewCompat.addWebMessageListener(webView, "replyWithMessagePortObject",
+                    Arrays.asList("https://example.com"),
+                    new MessagePortMessageListener(this, mPortButton));
+            WebViewCompat.addWebMessageListener(webView, "toastObject",
+                    Arrays.asList("https://example.com"), new ToastMessageListener(this));
+            WebViewCompat.addWebMessageListener(webView, "multipleMessagesObject",
+                    Arrays.asList("https://example.com"),
+                    new MultipleMessagesListener(this, mTextView));
+        }
+
+        webView.loadUrl(
+                Uri.withAppendedPath(mExampleUri, "www/web_message_listener.html").toString());
+    }
+
+    private static CharSequence createNativeTitle() {
+        final String title = "Native View";
+        SpannableString ss = new SpannableString(title);
+        ss.setSpan(new AbsoluteSizeSpan(55, true), 0, title.length(),
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        return ss;
+    }
+}
diff --git a/webkit/integration-tests/testapp/src/main/res/layout/activity_web_message_listener.xml b/webkit/integration-tests/testapp/src/main/res/layout/activity_web_message_listener.xml
new file mode 100644
index 0000000..1c64937
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/res/layout/activity_web_message_listener.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/activity_web_message_listener"
+    android:orientation="vertical"
+    android:weightSum="3"
+    android:background="#FF0"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <WebView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/webview"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="2"/>
+    <TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/textview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"/>
+    <LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:gravity="center"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1">
+        <Button
+            android:id="@+id/button_reply_proxy"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:text="Reply via ReplyProxy" />
+        <Button
+            android:id="@+id/button_port"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignBottom="@+id/button1"
+            android:text="Reply via Port" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/webkit/integration-tests/testapp/src/main/res/values/strings.xml b/webkit/integration-tests/testapp/src/main/res/values/strings.xml
index 7ac3fe6..52ab787 100644
--- a/webkit/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/webkit/integration-tests/testapp/src/main/res/values/strings.xml
@@ -70,4 +70,5 @@
     <string name="renderer_terminated_description">WebView renderer was terminated.</string>
     <string name="renderer_unresponsive_description">WebView renderer has become unresponsive.</string>
     <string name="fullscreen_activity_title">Fullscreen web contents Demo</string>
+    <string name="web_message_listener_activity_title">WebMessageListener Demo</string>
 </resources>