Merge "Provide ability for tests to send hardware key events to Owner" into androidx-master-dev
diff --git a/benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt b/benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt
index 920aea7..4aaa75c 100644
--- a/benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/CpuInfo.kt
@@ -65,7 +65,7 @@
                 // scaling_min_freq, or -1 if can't access
                 currentMinFreq = readFileTextOrNull("$path/cpufreq/scaling_min_freq")?.toInt()
                     ?: -1,
-                maxFreqKhz = readFileTextOrNull("$path/cpuinfo_max_freq")?.toLong() ?: -1L
+                maxFreqKhz = readFileTextOrNull("$path/cpufreq/cpuinfo_max_freq")?.toLong() ?: -1L
             )
         } ?: emptyList()
 
@@ -75,10 +75,8 @@
             ?.maxFreqKhz?.times(1000) ?: -1
 
         locked = isCpuLocked(coreDirs)
-        if (!locked) {
-            coreDirs.forEachIndexed { index, coreDir ->
-                Log.d(TAG, "cpu$index $coreDir")
-            }
+        coreDirs.forEachIndexed { index, coreDir ->
+            Log.d(TAG, "cpu$index $coreDir")
         }
     }
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 3628eb0..f56db6d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -62,7 +62,7 @@
     prebuilts(LibraryGroups.CORE, "core-ktx", "1.5.0-alpha01")
     prebuilts(LibraryGroups.CORE, "core-role", "1.1.0-alpha02")
     prebuilts(LibraryGroups.CURSORADAPTER, "1.0.0")
-    prebuilts(LibraryGroups.CUSTOMVIEW, "1.1.0-rc01")
+    prebuilts(LibraryGroups.CUSTOMVIEW, "1.1.0")
     prebuilts(LibraryGroups.DOCUMENTFILE, "1.0.0")
     prebuilts(LibraryGroups.DRAWERLAYOUT, "1.1.0-rc01")
     prebuilts(LibraryGroups.DYNAMICANIMATION, "dynamicanimation-ktx", "1.0.0-alpha03")
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index a229644..a1bc83d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -109,7 +109,6 @@
 
         // The list of checks that are API lint warnings and are yet to be enabled
         "ExecutorRegistration",
-        "NotCloseable",
         "SamShouldBeLast",
         "MissingJvmstatic",
         "CallbackMethodName",
@@ -134,6 +133,7 @@
         "ListenerLast",
         "StreamFiles",
         "AbstractInner",
+        "NotCloseable",
         "ArrayReturn",
         "MethodNameTense",
         "UseIcu",
diff --git a/busytown/androidx-studio-integration.sh b/busytown/androidx-studio-integration.sh
index fc84262..2a57e15 100755
--- a/busytown/androidx-studio-integration.sh
+++ b/busytown/androidx-studio-integration.sh
@@ -32,5 +32,7 @@
 export JAVA_TOOLS_JAR="$JAVA_HOME/lib/tools.jar"
 export LINT_PRINT_STACKTRACE=true
 
-$gw -p frameworks/support --no-daemon bOS --stacktrace -Pandroidx.allWarningsAsErrors
-DIST_SUBDIR="/ui" $gw -p frameworks/support/ui --no-daemon bOS --stacktrace -Pandroidx.allWarningsAsErrors
+LOG_SIMPLIFIER="$SCRIPT_DIR/../development/build_log_simplifier.sh"
+
+"$LOG_SIMPLIFIER" $gw -p frameworks/support --no-daemon bOS --stacktrace -Pandroidx.allWarningsAsErrors
+"$LOG_SIMPLIFIER" DIST_SUBDIR="/ui" $gw -p frameworks/support/ui --no-daemon bOS --stacktrace -Pandroidx.allWarningsAsErrors
diff --git a/busytown/impl/build.sh b/busytown/impl/build.sh
index 4ec9bde..c400e90 100755
--- a/busytown/impl/build.sh
+++ b/busytown/impl/build.sh
@@ -30,7 +30,7 @@
 # display the contents of the out/ directory, to allow us to be sure that it was empty before this build started
 echoAndDo "ls -la out"
 
-# run gradle
-# "androidx.summarizeStderr" outputs an error summary message. See gradlew for details
-echoAndDo OUT_DIR=out/ui DIST_DIR=$DIST_DIR/ui ANDROID_HOME=./prebuilts/fullsdk-linux frameworks/support/ui/gradlew -p frameworks/support/ui --stacktrace -Pandroidx.summarizeStderr "$@"
-echoAndDo OUT_DIR=out    DIST_DIR=$DIST_DIR    ANDROID_HOME=./prebuilts/fullsdk-linux frameworks/support/gradlew    -p frameworks/support    --stacktrace -Pandroidx.summarizeStderr "$@"
+LOG_SIMPLIFIER="$SCRIPT_DIR/../../development/build_log_simplifier.sh"
+
+"$LOG_SIMPLIFIER" OUT_DIR=out/ui DIST_DIR=$DIST_DIR/ui ANDROID_HOME=./prebuilts/fullsdk-linux frameworks/support/ui/gradlew -p frameworks/support/ui --stacktrace -Pandroidx.summarizeStderr "$@"
+"$LOG_SIMPLIFIER" OUT_DIR=out    DIST_DIR=$DIST_DIR    ANDROID_HOME=./prebuilts/fullsdk-linux frameworks/support/gradlew    -p frameworks/support    --stacktrace -Pandroidx.summarizeStderr "$@"
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
index 125d103..e5f9a13 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
@@ -17,19 +17,30 @@
 package androidx.camera.camera2.pipe
 
 import android.content.Context
+import android.os.HandlerThread
 import androidx.camera.camera2.pipe.impl.CameraGraphModule
 import androidx.camera.camera2.pipe.impl.CameraPipeComponent
 import androidx.camera.camera2.pipe.impl.CameraPipeModule
 import androidx.camera.camera2.pipe.impl.DaggerCameraPipeComponent
 
 /**
- * Interface to configure and access the camera device wrappers.
+ * [CameraPipe] is the top level scope for all interactions with a Camera2 camera.
+ *
+ * Under most circumstances an application should only ever have a single instance of a [CameraPipe]
+ * object as each instance will cache expensive calls and operations with the Android Camera
+ * framework. In addition to the caching behaviors it will optimize the access and configuration of
+ * [android.hardware.camera2.CameraDevice] and [android.hardware.camera2.CameraCaptureSession] via
+ * the [CameraGraph] interface.
  */
-class CameraPipe(private val config: CameraPipe.Config) {
+class CameraPipe(config: Config) {
     private val component: CameraPipeComponent = DaggerCameraPipeComponent.builder()
         .cameraPipeModule(CameraPipeModule(config))
         .build()
 
+    /**
+     * This creates a new [CameraGraph] that can be used to interact with a single Camera on the
+     * device. Multiple [CameraGraph]s can be created, but only one should be active at a time.
+     */
     fun create(config: CameraGraph.Config): CameraGraph {
         return component.cameraGraphComponentBuilder()
             .cameraGraphModule(CameraGraphModule(config))
@@ -37,7 +48,19 @@
             .cameraGraph()
     }
 
+    /**
+     * This provides access to information about the available cameras on the device.
+     */
+    fun cameras(): Cameras {
+        return component.cameras()
+    }
+
+    /**
+     * This is the application level configuration for [CameraPipe]. Nullable values are optional
+     * and reasonable defaults will be provided if the values are not specified.
+     */
     data class Config(
-        val appContext: Context
+        val appContext: Context,
+        val cameraThread: HandlerThread? = null
     )
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Cameras.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Cameras.kt
new file mode 100644
index 0000000..f82b0cf
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Cameras.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe
+
+/**
+ * Methods for querying, iterating, and selecting the Cameras that are available on the device.
+ */
+interface Cameras {
+    /**
+     * Iterate and return a list of CameraId's on the device that are capable of being opened. Some
+     * camera devices may be hidden or un-openable if they are included as part of a logical camera
+     * group.
+     */
+    fun findAll(): List<CameraId>
+
+    /**
+     * Load CameraMetadata for a specific CameraId. Loading CameraMetadata can take a
+     * non-zero amount of time to execute. If CameraMetadata is not already cached this function
+     * will suspend until CameraMetadata can be loaded.
+     */
+    suspend fun getMetadata(camera: CameraId): CameraMetadata
+
+    /**
+     * Load CameraMetadata for a specific CameraId and block the calling thread until the result is
+     * available.
+     */
+    fun awaitMetadata(camera: CameraId): CameraMetadata
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
index e4c92e5..94efcef 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
@@ -19,6 +19,7 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.StreamConfigurationMap
 import androidx.camera.camera2.pipe.impl.Debug
 import java.util.concurrent.ConcurrentHashMap
 
@@ -32,9 +33,8 @@
  */
 interface Metadata {
     operator fun <T> get(key: Key<T>): T?
-
-    fun <T> getOrDefault(key: Key<T>, default: T): T
     fun <T> getChecked(key: Key<T>): T
+    fun <T> getOrDefault(key: Key<T>, default: T): T
 
     /**
      * Metadata keys provide values or controls that are provided or computed by CameraPipe.
@@ -63,14 +63,26 @@
 
 /**
  * CameraMetadata is a wrapper around [CameraCharacteristics].
+ *
+ * In some cases the properties on this interface will provide faster or more backwards compatible
+ * access to features that are only available on newer versions of the OS.
  */
 interface CameraMetadata : Metadata, UnsafeWrapper<CameraCharacteristics> {
     operator fun <T> get(key: CameraCharacteristics.Key<T>): T?
-
-    fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T
     fun <T> getChecked(key: CameraCharacteristics.Key<T>): T
+    fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T
 
     val camera: CameraId
+    val isRedacted: Boolean
+
+    val keys: Set<CameraCharacteristics.Key<*>>
+    val requestKeys: Set<CaptureRequest.Key<*>>
+    val resultKeys: Set<CaptureResult.Key<*>>
+    val sessionKeys: Set<CaptureRequest.Key<*>>
+    val physicalCameraIds: Set<CameraId>
+    val physicalRequestKeys: Set<CaptureRequest.Key<*>>
+
+    val streamMap: StreamConfigurationMap
 }
 
 /**
@@ -78,9 +90,8 @@
  */
 interface RequestMetadata : Metadata, UnsafeWrapper<CaptureRequest> {
     operator fun <T> get(key: CaptureRequest.Key<T>): T?
-
-    fun <T> getOrDefault(key: CaptureRequest.Key<T>, default: T): T
     fun <T> getChecked(key: CaptureRequest.Key<T>): T
+    fun <T> getOrDefault(key: CaptureRequest.Key<T>, default: T): T
 }
 
 /**
@@ -88,9 +99,8 @@
  */
 interface ResultMetadata : Metadata, UnsafeWrapper<CaptureResult> {
     operator fun <T> get(key: CaptureResult.Key<T>): T?
-
-    fun <T> getOrDefault(key: CaptureResult.Key<T>, default: T): T
     fun <T> getChecked(key: CaptureResult.Key<T>): T
+    fun <T> getOrDefault(key: CaptureResult.Key<T>, default: T): T
 
     val camera: CameraId
     val request: RequestMetadata
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt
new file mode 100644
index 0000000..40cd98b
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.impl
+
+import android.Manifest.permission
+import android.content.Context
+import android.content.pm.PackageManager
+import android.hardware.camera2.CameraManager
+import android.os.Build
+import android.util.ArrayMap
+import androidx.annotation.GuardedBy
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Provides caching and querying of CameraMetadata.
+ *
+ * This class is designed to be thread safe and provides suspend functions for querying and
+ * accessing CameraMetadata.
+ */
+@Singleton
+class CameraMetadataCache @Inject constructor(
+    private val context: Context
+) {
+    @GuardedBy("cache")
+    private val cache = ArrayMap<String, CameraMetadata>()
+
+    @Volatile
+    private var hasCameraPermission = false
+
+    suspend fun get(cameraId: CameraId): CameraMetadata {
+        synchronized(cache) {
+            val existing = cache[cameraId.value]
+            if (existing != null) {
+                return existing
+            }
+        }
+
+        // Suspend and query CameraMetadata on a background thread.
+        return withContext(Dispatchers.IO) {
+            awaitMetadata(cameraId)
+        }
+    }
+
+    fun awaitMetadata(cameraId: CameraId): CameraMetadata {
+        return Debug.trace("awaitMetadata") {
+            synchronized(cache) {
+                val existing = cache[cameraId.value]
+                if (existing != null) {
+                    return@trace existing
+                } else if (!isMetadataRedacted()) {
+                    val result = createCameraMetadata(cameraId, false)
+                    cache[cameraId.value] = result
+                    return@trace result
+                }
+            }
+            return@trace createCameraMetadata(cameraId, true)
+        }
+    }
+
+    private fun createCameraMetadata(cameraId: CameraId, redacted: Boolean): CameraMetadataImpl {
+        val cameraManager =
+            context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+        val characteristics =
+            cameraManager.getCameraCharacteristics(cameraId.value)
+        return CameraMetadataImpl(cameraId, redacted, characteristics, emptyMap())
+    }
+
+    private fun isMetadataRedacted(): Boolean {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            // Some CameraCharacteristic properties are redacted on Q or higher if the application
+            // does not currently hold the CAMERA permission.
+            return !checkCameraPermission()
+        }
+        return false
+    }
+
+    @RequiresApi(23)
+    private fun checkCameraPermission(): Boolean {
+        // Granted camera permission is cached here to reduce the number of binder transactions
+        // executed.  This is considered okay because when a user revokes a permission at runtime,
+        // Android's PermissionManagerService kills the app via the onPermissionRevoked callback,
+        // allowing the code to avoid re-querying after checkSelfPermission returns true.
+        if (!hasCameraPermission &&
+            context.checkSelfPermission(permission.CAMERA) == PackageManager.PERMISSION_GRANTED
+        ) {
+            hasCameraPermission = true
+        }
+        return hasCameraPermission
+    }
+}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt
new file mode 100644
index 0000000..3e761b9
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.impl
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.os.Build
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.Metadata
+
+/**
+ * This implementation provides access to CameraCharacteristics and lazy caching of properties
+ * that are either expensive to create and access, or that only exist on newer versions of the
+ * OS.
+ */
+class CameraMetadataImpl constructor(
+    override val camera: CameraId,
+    override val isRedacted: Boolean,
+    private val characteristics: CameraCharacteristics,
+    private val metadata: Map<Metadata.Key<*>, Any?>
+) : CameraMetadata {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T> get(key: Metadata.Key<T>): T? = metadata[key] as T?
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T> getChecked(key: Metadata.Key<T>): T = (metadata[key] as T)!!
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T> getOrDefault(key: Metadata.Key<T>, default: T): T =
+        metadata[key] as T? ?: default
+
+    override fun <T> get(key: CameraCharacteristics.Key<T>): T? = characteristics[key]
+    override fun <T> getChecked(key: CameraCharacteristics.Key<T>): T = characteristics[key]!!
+    override fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T =
+        characteristics[key] ?: default
+
+    override fun unwrap(): CameraCharacteristics? = characteristics
+
+    override val keys: Set<CameraCharacteristics.Key<*>> get() = _keys.value
+    override val requestKeys: Set<CaptureRequest.Key<*>> get() = _requestKeys.value
+    override val resultKeys: Set<CaptureResult.Key<*>> get() = _resultKeys.value
+    override val sessionKeys: Set<CaptureRequest.Key<*>> get() = _sessionKeys.value
+    override val physicalCameraIds: Set<CameraId> get() = _physicalCameraIds.value
+    override val physicalRequestKeys: Set<CaptureRequest.Key<*>>
+        get() = _physicalRequestKeys.value
+
+    override val streamMap: StreamConfigurationMap get() = _streamMap.value
+
+    private val _keys: Lazy<Set<CameraCharacteristics.Key<*>>> =
+        lazy(LazyThreadSafetyMode.PUBLICATION) {
+            try {
+                @Suppress("UselessCallOnNotNull")
+                characteristics.keys.orEmpty().toSet()
+            } catch (ignored: AssertionError) {
+                emptySet<CameraCharacteristics.Key<*>>()
+            }
+        }
+
+    private val _requestKeys: Lazy<Set<CaptureRequest.Key<*>>> =
+        lazy(LazyThreadSafetyMode.PUBLICATION) {
+            try {
+                @Suppress("UselessCallOnNotNull")
+                characteristics.availableCaptureRequestKeys.orEmpty().toSet()
+            } catch (ignored: AssertionError) {
+                emptySet<CaptureRequest.Key<*>>()
+            }
+        }
+
+    private val _resultKeys: Lazy<Set<CaptureResult.Key<*>>> =
+        lazy(LazyThreadSafetyMode.PUBLICATION) {
+            try {
+                @Suppress("UselessCallOnNotNull")
+                characteristics.availableCaptureResultKeys.orEmpty().toSet()
+            } catch (ignored: AssertionError) {
+                emptySet<CaptureResult.Key<*>>()
+            }
+        }
+
+    private val _physicalCameraIds: Lazy<Set<CameraId>> =
+        lazy(LazyThreadSafetyMode.PUBLICATION) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+                emptySet()
+            } else {
+                try {
+                    @Suppress("UselessCallOnNotNull")
+                    characteristics.physicalCameraIds.orEmpty().map { CameraId(it) }.toSet()
+                } catch (ignored: AssertionError) {
+                    emptySet<CameraId>()
+                }
+            }
+        }
+
+    private val _physicalRequestKeys: Lazy<Set<CaptureRequest.Key<*>>> =
+        lazy(LazyThreadSafetyMode.PUBLICATION) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+                emptySet()
+            } else {
+                try {
+                    @Suppress("UselessCallOnNotNull")
+                    characteristics.availablePhysicalCameraRequestKeys.orEmpty().toSet()
+                } catch (ignored: AssertionError) {
+                    emptySet<CaptureRequest.Key<*>>()
+                }
+            }
+        }
+
+    private val _sessionKeys: Lazy<Set<CaptureRequest.Key<*>>> =
+        lazy(LazyThreadSafetyMode.PUBLICATION) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+                emptySet()
+            } else {
+                try {
+                    @Suppress("UselessCallOnNotNull")
+                    characteristics.availableSessionKeys.orEmpty().toSet()
+                } catch (ignored: AssertionError) {
+                    emptySet<CaptureRequest.Key<*>>()
+                }
+            }
+        }
+
+    private val _streamMap: Lazy<StreamConfigurationMap> =
+        lazy(LazyThreadSafetyMode.PUBLICATION) {
+            getChecked(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
+        }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt
index 5470d42..3cd222f 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt
@@ -17,23 +17,44 @@
 package androidx.camera.camera2.pipe.impl
 
 import android.content.Context
+import android.hardware.camera2.CameraManager
 import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.Cameras
+import dagger.Binds
 import dagger.Component
 import dagger.Module
 import dagger.Provides
+import dagger.Reusable
 import javax.inject.Singleton
 
 @Singleton
 @Component(modules = [CameraPipeModule::class])
 interface CameraPipeComponent {
     fun cameraGraphComponentBuilder(): CameraGraphComponent.Builder
+    fun cameras(): Cameras
 }
 
-@Module(subcomponents = [CameraGraphComponent::class])
+@Module(
+    includes = [CameraPipeBindings::class],
+    subcomponents = [CameraGraphComponent::class]
+)
 class CameraPipeModule(private val config: CameraPipe.Config) {
     @Provides
     fun provideCameraPipeConfig(): CameraPipe.Config = config
+}
 
-    @Provides
-    fun provideCameraPipeContext(): Context = config.appContext
+@Module
+abstract class CameraPipeBindings {
+    @Binds
+    abstract fun bindCameras(impl: CamerasImpl): Cameras
+
+    companion object {
+        @Provides
+        fun provideContext(config: CameraPipe.Config): Context = config.appContext
+
+        @Reusable
+        @Provides
+        fun provideCameraManager(context: Context): CameraManager =
+            context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+    }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CamerasImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CamerasImpl.kt
new file mode 100644
index 0000000..75c9d10
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CamerasImpl.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.impl
+
+import android.hardware.camera2.CameraManager
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.Cameras
+import androidx.camera.camera2.pipe.impl.Debug.trace
+import javax.inject.Inject
+import javax.inject.Provider
+import javax.inject.Singleton
+
+/**
+ * Provides utilities for querying cameras and accessing metadata about those cameras.
+ */
+@Singleton
+class CamerasImpl @Inject constructor(
+    private val cameraManager: Provider<CameraManager>,
+    private val metadata: CameraMetadataCache
+) : Cameras {
+
+    private val cameras = lazy(LazyThreadSafetyMode.PUBLICATION) {
+        // NOTE: Publication safety mode may cause this method to be invoked more than once if there
+        //  a race between multiple threads. Only one return value will ultimately be cached.
+
+        trace("cameraIdList") {
+            val cameraManager = cameraManager.get()
+
+            // TODO(b/159052778): Find a way to make this poll for the camera list and to suspend
+            //  if the value is not yet available. It will be important to detect and differentiate
+            //  between devices that should have cameras, but don't, vs devices that do not
+            //  physically have cameras. It may also be worthwhile to re-query if looking for
+            //  external cameras.
+
+            // WARNING: This method can, at times, return an empty list of cameras on devices that
+            //  will normally return a valid list of cameras (b/159052778)
+            val cameraIdList = cameraManager.cameraIdList
+            cameraIdList.map { CameraId(it) }
+        }
+    }
+
+    override fun findAll(): List<CameraId> = cameras.value
+
+    override suspend fun getMetadata(camera: CameraId): CameraMetadata {
+        return metadata.get(camera)
+    }
+
+    override fun awaitMetadata(camera: CameraId): CameraMetadata {
+        return metadata.awaitMetadata(camera)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt
index 069ecf1..f331631 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt
@@ -18,11 +18,34 @@
 
 package androidx.camera.camera2.pipe.impl
 
+import android.os.Trace
+
 /**
  * Internal debug utilities, constants, and checks.
  */
 object Debug {
     const val ENABLE_LOGGING = true
+    const val ENABLE_TRACING = true
+
+    /**
+     * Wrap the specified [block] in calls to [Trace.beginSection] (with the supplied [label])
+     * and [Trace.endSection].
+     *
+     * @param label A name of the code section to appear in the trace.
+     * @param block A block of code which is being traced.
+     */
+    inline fun <T> trace(label: String, crossinline block: () -> T): T {
+        try {
+            if (ENABLE_TRACING) {
+                Trace.beginSection(label)
+            }
+            return block()
+        } finally {
+            if (ENABLE_TRACING) {
+                Trace.endSection()
+            }
+        }
+    }
 
     /**
      * Asserts that the provided value *is* null.
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
index 2d9013a..c7292b4 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
@@ -49,4 +49,15 @@
         )
         assertThat(cameraGraph).isNotNull()
     }
+
+    @Test
+    fun iterateCameraIds() {
+        val context = ApplicationProvider.getApplicationContext() as Context
+        val cameraPipe = CameraPipe(CameraPipe.Config(context))
+        val cameras = cameraPipe.cameras()
+        val cameraList = cameras.findAll()
+
+        assertThat(cameraList).isNotNull()
+        assertThat(cameraList.size).isEqualTo(0) // Robolectric does not report cameras.
+    }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt
index dce2657..35a2da1 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt
@@ -19,9 +19,9 @@
 import android.content.Context
 import android.os.Build
 import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
 import androidx.test.core.app.ApplicationProvider
-import androidx.camera.camera2.pipe.CameraId
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraMetadataCacheTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraMetadataCacheTest.kt
new file mode 100644
index 0000000..745e8c9
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraMetadataCacheTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.impl
+
+import android.Manifest
+import android.app.Application
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.os.Build
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+import org.robolectric.shadows.ShadowApplication
+import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.shadows.ShadowCameraManager
+
+@SmallTest
+@RunWith(CameraPipeRobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class CameraMetadataCacheTest {
+    companion object {
+        const val CAMERA0_ID = "0"
+        const val CAMERA0_SUPPORTED_HARDWARE_LEVEL =
+            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
+        const val CAMERA0_SENSOR_ORIENTATION = 90
+        const val CAMERA0_LENS_FACING = CameraCharacteristics.LENS_FACING_BACK
+        const val CAMERA0_FLASH_INFO_AVAILABLE = true
+
+        const val CAMERA1_ID = "1"
+        const val CAMERA1_SUPPORTED_HARDWARE_LEVEL =
+            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
+        const val CAMERA1_SENSOR_ORIENTATION = 0
+        const val CAMERA1_LENS_FACING_INT = CameraCharacteristics.LENS_FACING_FRONT
+        const val CAMERA1_FLASH_INFO_AVAILABLE = false
+    }
+
+    @Test
+    fun metadataIsCachedAndShimmed() {
+        configureShadowCameras()
+        val context = ApplicationProvider.getApplicationContext<Context>()
+        val cache = CameraMetadataCache(context)
+
+        val metadata0 = cache.awaitMetadata(CameraId("0"))
+        val metadata1 = cache.awaitMetadata(CameraId("1"))
+
+        // Check to make sure that metadata is not null, and that various properties do not crash
+        // on older OS versions when accessed.
+        assertThat(metadata0).isNotNull()
+        assertThat(metadata0.camera).isEqualTo(CameraId("0"))
+        assertThat(metadata0.isRedacted).isFalse()
+        assertThat(metadata0.keys).isNotNull()
+        assertThat(metadata0.requestKeys).isNotNull()
+        assertThat(metadata0.resultKeys).isNotNull()
+        assertThat(metadata0.sessionKeys).isNotNull()
+        assertThat(metadata0.physicalCameraIds).isNotNull()
+        assertThat(metadata0.physicalRequestKeys).isNotNull()
+        assertThat(metadata0[CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL]).isEqualTo(
+            CAMERA0_SUPPORTED_HARDWARE_LEVEL
+        )
+
+        assertThat(metadata1).isNotNull()
+        assertThat(metadata1.camera).isEqualTo(CameraId("1"))
+        assertThat(metadata1.isRedacted).isFalse()
+        assertThat(metadata1.keys).isNotNull()
+        assertThat(metadata1.requestKeys).isNotNull()
+        assertThat(metadata1.resultKeys).isNotNull()
+        assertThat(metadata1.sessionKeys).isNotNull()
+        assertThat(metadata1.physicalCameraIds).isNotNull()
+        assertThat(metadata1.physicalRequestKeys).isNotNull()
+        assertThat(metadata1[CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL]).isEqualTo(
+            CAMERA1_SUPPORTED_HARDWARE_LEVEL
+        )
+    }
+
+    private fun configureShadowCameras() {
+        val app: Application =
+            ApplicationProvider.getApplicationContext()
+        val shadowApp: ShadowApplication = Shadows.shadowOf(app)
+        shadowApp.grantPermissions(Manifest.permission.CAMERA)
+
+        val shadowCameraManager = Shadow.extract<Any>(
+            app.getSystemService(Context.CAMERA_SERVICE)
+        ) as ShadowCameraManager
+
+        val characteristics0 = ShadowCameraCharacteristics.newCameraCharacteristics()
+        val shadowCharacteristics0 = Shadow.extract<ShadowCameraCharacteristics>(characteristics0)
+        shadowCharacteristics0.set(
+            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL,
+            CAMERA0_SUPPORTED_HARDWARE_LEVEL
+        )
+        // Add a lens facing to the camera
+        shadowCharacteristics0.set(
+            CameraCharacteristics.LENS_FACING,
+            CAMERA0_LENS_FACING
+        )
+        // Mock the sensor orientation
+        shadowCharacteristics0.set(
+            CameraCharacteristics.SENSOR_ORIENTATION,
+            CAMERA0_SENSOR_ORIENTATION
+        )
+        // Mock the flash unit availability
+        shadowCharacteristics0.set(
+            CameraCharacteristics.FLASH_INFO_AVAILABLE,
+            CAMERA0_FLASH_INFO_AVAILABLE
+        )
+        // Add the camera to the camera service
+        shadowCameraManager.addCamera(CAMERA0_ID, characteristics0)
+
+        // **** Camera 1 characteristics ****//
+        val characteristics1 =
+            ShadowCameraCharacteristics.newCameraCharacteristics()
+        val shadowCharacteristics1 =
+            Shadow.extract<ShadowCameraCharacteristics>(characteristics1)
+        shadowCharacteristics1.set(
+            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL,
+            CAMERA1_SUPPORTED_HARDWARE_LEVEL
+        )
+        // Add a lens facing to the camera
+        shadowCharacteristics1.set(
+            CameraCharacteristics.LENS_FACING,
+            CAMERA1_LENS_FACING_INT
+        )
+        // Mock the sensor orientation
+        shadowCharacteristics1.set(
+            CameraCharacteristics.SENSOR_ORIENTATION,
+            CAMERA1_SENSOR_ORIENTATION
+        )
+        // Mock the flash unit availability
+        shadowCharacteristics1.set(
+            CameraCharacteristics.FLASH_INFO_AVAILABLE,
+            CAMERA1_FLASH_INFO_AVAILABLE
+        )
+        // Add the camera to the camera service
+        shadowCameraManager.addCamera(CAMERA1_ID, characteristics1)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
index 54ec2ab..f88d2c9 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
@@ -21,6 +21,7 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.StreamConfigurationMap
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
 import androidx.camera.camera2.pipe.Metadata
@@ -65,6 +66,16 @@
 
     override fun <T> getChecked(key: CameraCharacteristics.Key<T>): T = (values[key] as T)!!
     override val camera = CameraId("Fake")
+    override val isRedacted = false
+    override val keys: Set<CameraCharacteristics.Key<*>> = emptySet()
+    override val requestKeys: Set<CaptureRequest.Key<*>> = emptySet()
+    override val resultKeys: Set<CaptureResult.Key<*>> = emptySet()
+    override val sessionKeys: Set<CaptureRequest.Key<*>> = emptySet()
+    override val physicalCameraIds: Set<CameraId> = emptySet()
+    override val physicalRequestKeys: Set<CaptureRequest.Key<*>> = emptySet()
+    override val streamMap: StreamConfigurationMap
+        get() = throw UnsupportedOperationException("StreamConfigurationMap is not available.")
+
     override fun unwrap(): CameraCharacteristics? {
         throw UnsupportedOperationException(
             "FakeCameraMetadata does not wrap CameraCharacteristics")
diff --git a/camera/camera-core/api/public_plus_experimental_1.0.0-beta06.txt b/camera/camera-core/api/public_plus_experimental_1.0.0-beta06.txt
index 2b746ba..8bec130 100644
--- a/camera/camera-core/api/public_plus_experimental_1.0.0-beta06.txt
+++ b/camera/camera-core/api/public_plus_experimental_1.0.0-beta06.txt
@@ -93,6 +93,9 @@
   @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
   }
 
+  @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroup {
+  }
+
   public interface ExtendableBuilder<T> {
     method public T build();
   }
@@ -311,6 +314,35 @@
   public abstract class UseCase {
   }
 
+  @androidx.camera.core.ExperimentalUseCaseGroup public final class UseCaseGroup {
+    method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
+    method public androidx.camera.core.ViewPort getViewPort();
+  }
+
+  @androidx.camera.core.ExperimentalUseCaseGroup public static class UseCaseGroup.Builder {
+    ctor public UseCaseGroup.Builder();
+    method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
+    method public androidx.camera.core.UseCaseGroup build();
+    method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
+  }
+
+  @androidx.camera.core.ExperimentalUseCaseGroup public final class ViewPort {
+    method public android.util.Rational getAspectRatio();
+    method public int getRotation();
+    method public int getScaleType();
+    field public static final int FILL_CENTER = 1; // 0x1
+    field public static final int FILL_END = 2; // 0x2
+    field public static final int FILL_START = 0; // 0x0
+    field public static final int FIT_CENTER = 4; // 0x4
+    field public static final int FIT_END = 5; // 0x5
+    field public static final int FIT_START = 3; // 0x3
+  }
+
+  @androidx.camera.core.ExperimentalUseCaseGroup public static class ViewPort.Builder {
+    ctor public ViewPort.Builder(android.util.Rational, int);
+    method public androidx.camera.core.ViewPort build();
+  }
+
   public interface ZoomState {
     method public float getLinearZoom();
     method public float getMaxZoomRatio();
diff --git a/camera/camera-core/api/public_plus_experimental_current.txt b/camera/camera-core/api/public_plus_experimental_current.txt
index 2b746ba..8bec130 100644
--- a/camera/camera-core/api/public_plus_experimental_current.txt
+++ b/camera/camera-core/api/public_plus_experimental_current.txt
@@ -93,6 +93,9 @@
   @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
   }
 
+  @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroup {
+  }
+
   public interface ExtendableBuilder<T> {
     method public T build();
   }
@@ -311,6 +314,35 @@
   public abstract class UseCase {
   }
 
+  @androidx.camera.core.ExperimentalUseCaseGroup public final class UseCaseGroup {
+    method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
+    method public androidx.camera.core.ViewPort getViewPort();
+  }
+
+  @androidx.camera.core.ExperimentalUseCaseGroup public static class UseCaseGroup.Builder {
+    ctor public UseCaseGroup.Builder();
+    method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
+    method public androidx.camera.core.UseCaseGroup build();
+    method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
+  }
+
+  @androidx.camera.core.ExperimentalUseCaseGroup public final class ViewPort {
+    method public android.util.Rational getAspectRatio();
+    method public int getRotation();
+    method public int getScaleType();
+    field public static final int FILL_CENTER = 1; // 0x1
+    field public static final int FILL_END = 2; // 0x2
+    field public static final int FILL_START = 0; // 0x0
+    field public static final int FIT_CENTER = 4; // 0x4
+    field public static final int FIT_END = 5; // 0x5
+    field public static final int FIT_START = 3; // 0x3
+  }
+
+  @androidx.camera.core.ExperimentalUseCaseGroup public static class ViewPort.Builder {
+    ctor public ViewPort.Builder(android.util.Rational, int);
+    method public androidx.camera.core.ViewPort build();
+  }
+
   public interface ZoomState {
     method public float getLinearZoom();
     method public float getMaxZoomRatio();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index 124ce73..f73f4eb 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -215,6 +215,7 @@
     @RestrictTo(Scope.LIBRARY_GROUP)
     @SuppressWarnings({"lambdaLast", "unused"})
     @NonNull
+    @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
     public static Camera bindToLifecycle(
             @NonNull LifecycleOwner lifecycleOwner,
             @NonNull CameraSelector cameraSelector,
@@ -280,9 +281,10 @@
      *                                  camera to be used for the given use cases.
      * @hide
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @UseExperimental(markerClass = ExperimentalCameraFilter.class)
     @SuppressWarnings({"lambdaLast", "unused"})
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @ExperimentalUseCaseGroup
+    @UseExperimental(markerClass = ExperimentalCameraFilter.class)
     @NonNull
     public static Camera bindToLifecycle(
             @NonNull LifecycleOwner lifecycleOwner,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalUseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalUseCaseGroup.java
new file mode 100644
index 0000000..216a281
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalUseCaseGroup.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.experimental.Experimental;
+
+import java.lang.annotation.Retention;
+
+
+/**
+ * Denotes that the annotated classes and methods uses the experimental feature which provides
+ * a grouping mechanism for {@link UseCase}s.
+ *
+ * <p> The {@link UseCaseGroup} is a class that groups {@link UseCase}s together. All the
+ * {@link UseCase}s in the same group share certain properties. The {@link ViewPort} is a
+ * collection of shared {@link UseCase} properties for synchronizing the visible rectangle across
+ * all the use cases.
+ */
+@Retention(CLASS)
+@Experimental
+public @interface ExperimentalUseCaseGroup {
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
index da97999d..fb9d455 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
@@ -17,7 +17,6 @@
 package androidx.camera.core;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
 import androidx.core.util.Preconditions;
 import androidx.lifecycle.Lifecycle;
 
@@ -31,17 +30,15 @@
  * {@link UseCase}s to the same {@link Lifecycle}. {@link UseCase}s inside of a
  * {@link UseCaseGroup} usually share some common properties like the FOV defined by
  * {@link ViewPort}.
- *
- * @hide
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@ExperimentalUseCaseGroup
 public final class UseCaseGroup {
 
     private ViewPort mViewPort;
 
-    private UseCase[] mUseCases;
+    private List<UseCase> mUseCases;
 
-    UseCaseGroup(@NonNull ViewPort viewPort, @NonNull UseCase[] useCases) {
+    UseCaseGroup(@NonNull ViewPort viewPort, @NonNull List<UseCase> useCases) {
         mViewPort = viewPort;
         mUseCases = useCases;
     }
@@ -58,16 +55,14 @@
      * Gets the {@link UseCase}s.
      */
     @NonNull
-    public UseCase[] getUseCases() {
+    public List<UseCase> getUseCases() {
         return mUseCases;
     }
 
     /**
      * A builder for generating {@link UseCaseGroup}.
-     *
-     * @hide
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @ExperimentalUseCaseGroup
     public static class Builder {
 
         private ViewPort mViewPort;
@@ -80,11 +75,8 @@
 
         /**
          * Sets {@link ViewPort} shared by the {@link UseCase}s.
-         *
-         * @hide
          */
         @NonNull
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         public Builder setViewPort(@NonNull ViewPort viewPort) {
             mViewPort = viewPort;
             return this;
@@ -92,11 +84,8 @@
 
         /**
          * Adds {@link UseCase} to the collection.
-         *
-         * @hide
          */
         @NonNull
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         public Builder addUseCase(@NonNull UseCase useCase) {
             mUseCases.add(useCase);
             return this;
@@ -104,14 +93,11 @@
 
         /**
          * Builds a {@link UseCaseGroup} from the current state.
-         *
-         * @hide
          */
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @NonNull
         public UseCaseGroup build() {
             Preconditions.checkArgument(!mUseCases.isEmpty(), "UseCase must not be empty.");
-            return new UseCaseGroup(mViewPort, mUseCases.toArray(new UseCase[0]));
+            return new UseCaseGroup(mViewPort, mUseCases);
         }
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java b/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java
index 639b079d..095456a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java
@@ -17,6 +17,8 @@
 package androidx.camera.core;
 
 import android.util.Rational;
+import android.view.Display;
+import android.view.Surface;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -29,10 +31,8 @@
 
 /**
  * The FOV of one or many {@link UseCase}s.
- *
- * @hide
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@ExperimentalUseCaseGroup
 public final class ViewPort {
 
     /**
@@ -59,69 +59,65 @@
     }
 
     /**
-     * Scale the preview, maintaining the source aspect ratio, so it fills the entire
-     * container, and align it to the top left corner of the view.
-     * This may cause the preview to be cropped if the camera preview aspect ratio does not
-     * match that of its container.
-     *
-     * @hide
+     * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+     * ratio, so it fills the entire {@link ViewPort}, and align it to the start of the
+     * {@link ViewPort}, which is the top left corner in a left-to-right (LTR) layout, or the top
+     * right corner in a right-to-left (RTL) layout.
+     * <p>
+     * This may cause the output to be cropped if the output aspect ratio does not match that of
+     * the {@link ViewPort}.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final int FILL_START = 0;
 
     /**
-     * Scale the preview, maintaining the source aspect ratio, so it fills the entire
-     * container, and center it inside the view.
-     * This may cause the preview to be cropped if the camera preview aspect ratio does not
-     * match that of its container.
-     *
-     * @hide
+     * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+     * ratio, so it fills the entire {@link ViewPort} and center it.
+     * <p>
+     * This may cause the output to be cropped if the output aspect ratio does not match that of
+     * the {@link ViewPort}.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final int FILL_CENTER = 1;
 
     /**
-     * Scale the preview, maintaining the source aspect ratio, so it fills the entire
-     * container, and align it to the bottom right corner of the view.
-     * This may cause the preview to be cropped if the camera preview aspect ratio does not
-     * match that of its container.
-     *
-     * @hide
+     * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+     * ratio, so it fills the entire {@link ViewPort}, and align it to the end of the
+     * {@link ViewPort}, which is the bottom right corner in a left-to-right (LTR) layout, or the
+     * bottom left corner in a right-to-left (RTL) layout.
+     * <p>
+     * This may cause the output to be cropped if the output aspect ratio does not match that of
+     * the {@link ViewPort}.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final int FILL_END = 2;
 
     /**
-     * Scale the preview, maintaining the source aspect ratio, so it is entirely contained
-     * within the container, and align it to the top left corner of the view.
-     * Both dimensions of the preview will be equal or less than the corresponding dimensions
-     * of its container.
-     *
-     * @hide
+     * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+     * ratio, so it is entirely contained within the {@link ViewPort}, and align it to the start
+     * of the {@link ViewPort}, which is the top left corner in a left-to-right (LTR) layout, or
+     * the top right corner in a right-to-left (RTL) layout.
+     * <p>
+     * Both dimensions of the {@link ViewPort} crop rect will be equal or less than the
+     * corresponding dimensions of the output.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final int FIT_START = 3;
 
     /**
-     * Scale the preview, maintaining the source aspect ratio, so it is entirely contained
-     * within the container, and center it inside the view.
-     * Both dimensions of the preview will be equal or less than the corresponding dimensions
-     * of its container.
-     *
-     * @hide
+     * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+     * ratio, so it is entirely contained within the {@link ViewPort} and center it.
+     * <p>
+     * Both dimensions of the {@link ViewPort} will be equal or less than the corresponding
+     * dimensions of the output.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final int FIT_CENTER = 4;
 
     /**
-     * Scale the preview, maintaining the source aspect ratio, so it is entirely contained
-     * within the container, and align it to the bottom right corner of the view.
-     * Both dimensions of the preview will be equal or less than the corresponding dimensions
-     * of its container.
-     *
-     * @hide
+     * Generate a crop rect that once applied, it scales the output while maintaining its aspect
+     * ratio, so it is entirely contained within the {@link ViewPort}, and align it to the end of
+     * the {@link ViewPort}, which is the bottom right corner in a left-to-right (LTR) layout, or
+     * the bottom left corner in a right-to-left (RTL) layout.
+     * <p>
+     * Both dimensions of the {@link ViewPort} will be equal or less than the corresponding
+     * dimensions of the output.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final int FIT_END = 5;
 
     @ScaleType
@@ -146,32 +142,23 @@
 
     /**
      * Gets the aspect ratio of the {@link ViewPort}.
-     *
-     * @hide
      */
     @NonNull
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public Rational getAspectRatio() {
         return mAspectRatio;
     }
 
     /**
      * Gets the rotation of the {@link ViewPort}.
-     *
-     * @hide
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @ImageOutputConfig.RotationValue
     public int getRotation() {
         return mRotation;
     }
 
     /**
-     * Gets the {@link ScaleType} of the {@link ViewPort}.
-     *
-     * @hide
+     * Gets the scale type of the {@link ViewPort}.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @ScaleType
     public int getScaleType() {
         return mScaleType;
@@ -190,10 +177,8 @@
 
     /**
      * Builder for {@link ViewPort}.
-     *
-     * @hide
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @ExperimentalUseCaseGroup
     public static class Builder {
 
         private static final int DEFAULT_LAYOUT_DIRECTION = android.util.LayoutDirection.LTR;
@@ -212,6 +197,18 @@
         private int mLayoutDirection = DEFAULT_LAYOUT_DIRECTION;
 
 
+        /**
+         * Creates {@link ViewPort.Builder} with aspect ratio and rotation
+         *
+         * <p> The rotation value is relative to the "natural" rotation. This is one of four
+         * valid values: {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+         * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}. For example, if the desired
+         * the orientation is the gravity, the value should be set with the value
+         * of {@link Display#getRotation()}.
+         *
+         * <p> The aspect ratio is the desired crop rect after rotation is applied. In practice,
+         * this is usually the aspect ratio of the view finder.
+         */
         public Builder(@NonNull Rational aspectRatio,
                 @ImageOutputConfig.RotationValue int rotation) {
             mAspectRatio = aspectRatio;
@@ -221,13 +218,14 @@
         /**
          * Sets the {@link ScaleType} of the {@link ViewPort}.
          *
-         * <p> The value is used by {@link UseCase} to calculate the crop rect. The default value is
-         * {@link #FILL_CENTER} if not set.
+         * <p> The value is used by {@link UseCase} to calculate the
+         * {@link UseCase#getViewPortCropRect()}.
+         *
+         * <p> The default value is {@link #FILL_CENTER} if not set.
          *
          * @hide
          */
         @NonNull
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         public Builder setScaleType(@ScaleType int scaleType) {
             mScaleType = scaleType;
             return this;
@@ -245,7 +243,6 @@
          * @hide
          */
         @NonNull
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         public Builder setLayoutDirection(@LayoutDirection int layoutDirection) {
             mLayoutDirection = layoutDirection;
             return this;
@@ -253,10 +250,7 @@
 
         /**
          * Builds the {@link ViewPort}.
-         *
-         * @hide
          */
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @NonNull
         public ViewPort build() {
             Preconditions.checkNotNull(mAspectRatio, "The crop aspect ratio must be set.");
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index 1346c52..ae1a46b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -23,6 +23,7 @@
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.experimental.UseExperimental;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.ViewPort;
 import androidx.camera.core.impl.CameraControlInternal;
@@ -108,6 +109,7 @@
      * @throws CameraException Thrown if the combination of newly attached UseCases and the
      * currently attached UseCases exceed the capability of the camera.
      */
+    @UseExperimental(markerClass = androidx.camera.core.ExperimentalUseCaseGroup.class)
     public void attachUseCases(@NonNull Collection<UseCase> useCases) throws CameraException {
         synchronized (mLock) {
             List<UseCase> useCaseListAfterUpdate = new ArrayList<>(mAttachedUseCases);
diff --git a/camera/camera-lifecycle/api/1.0.0-beta06.txt b/camera/camera-lifecycle/api/1.0.0-beta06.txt
index 88187bc6..8278c48 100644
--- a/camera/camera-lifecycle/api/1.0.0-beta06.txt
+++ b/camera/camera-lifecycle/api/1.0.0-beta06.txt
@@ -2,7 +2,7 @@
 package androidx.camera.lifecycle {
 
   public final class ProcessCameraProvider {
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
     method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
     method public boolean isBound(androidx.camera.core.UseCase);
diff --git a/camera/camera-lifecycle/api/current.txt b/camera/camera-lifecycle/api/current.txt
index 88187bc6..8278c48 100644
--- a/camera/camera-lifecycle/api/current.txt
+++ b/camera/camera-lifecycle/api/current.txt
@@ -2,7 +2,7 @@
 package androidx.camera.lifecycle {
 
   public final class ProcessCameraProvider {
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
     method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
     method public boolean isBound(androidx.camera.core.UseCase);
diff --git a/camera/camera-lifecycle/api/public_plus_experimental_1.0.0-beta06.txt b/camera/camera-lifecycle/api/public_plus_experimental_1.0.0-beta06.txt
index 777baf2..b28a42b 100644
--- a/camera/camera-lifecycle/api/public_plus_experimental_1.0.0-beta06.txt
+++ b/camera/camera-lifecycle/api/public_plus_experimental_1.0.0-beta06.txt
@@ -4,8 +4,12 @@
   @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
   }
 
+  @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroupLifecycle {
+  }
+
   public final class ProcessCameraProvider {
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) @androidx.camera.lifecycle.ExperimentalUseCaseGroupLifecycle public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
     method @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
     method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
diff --git a/camera/camera-lifecycle/api/public_plus_experimental_current.txt b/camera/camera-lifecycle/api/public_plus_experimental_current.txt
index 777baf2..b28a42b 100644
--- a/camera/camera-lifecycle/api/public_plus_experimental_current.txt
+++ b/camera/camera-lifecycle/api/public_plus_experimental_current.txt
@@ -4,8 +4,12 @@
   @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
   }
 
+  @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroupLifecycle {
+  }
+
   public final class ProcessCameraProvider {
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) @androidx.camera.lifecycle.ExperimentalUseCaseGroupLifecycle public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
     method @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
     method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
diff --git a/camera/camera-lifecycle/api/restricted_1.0.0-beta06.txt b/camera/camera-lifecycle/api/restricted_1.0.0-beta06.txt
index 88187bc6..8278c48 100644
--- a/camera/camera-lifecycle/api/restricted_1.0.0-beta06.txt
+++ b/camera/camera-lifecycle/api/restricted_1.0.0-beta06.txt
@@ -2,7 +2,7 @@
 package androidx.camera.lifecycle {
 
   public final class ProcessCameraProvider {
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
     method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
     method public boolean isBound(androidx.camera.core.UseCase);
diff --git a/camera/camera-lifecycle/api/restricted_current.txt b/camera/camera-lifecycle/api/restricted_current.txt
index 88187bc6..8278c48 100644
--- a/camera/camera-lifecycle/api/restricted_current.txt
+++ b/camera/camera-lifecycle/api/restricted_current.txt
@@ -2,7 +2,7 @@
 package androidx.camera.lifecycle {
 
   public final class ProcessCameraProvider {
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread @experimental.UseExperimental(markerClass=ExperimentalUseCaseGroup.class) public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
     method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
     method public boolean isBound(androidx.camera.core.UseCase);
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ExperimentalUseCaseGroupLifecycle.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ExperimentalUseCaseGroupLifecycle.java
new file mode 100644
index 0000000..9b4891b
--- /dev/null
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ExperimentalUseCaseGroupLifecycle.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.lifecycle;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.experimental.Experimental;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Same as {@link androidx.camera.core.ExperimentalUseCaseGroup}. Should only be used in
+ * lifecycle artifact.
+ *
+ * <p> The duplication is to workaround an issue where experimental annotation from a
+ * different artifact cannot be properly applied. Adding this annotation hides the API from
+ * current.txt.
+ *
+ * @see androidx.camera.core.ExperimentalUseCaseGroup
+ *
+ * TODO(b/159033688): Remove after the bug is fixed.
+ */
+@Retention(CLASS)
+@Experimental
+public @interface ExperimentalUseCaseGroupLifecycle {
+}
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index b1d9283..858b1a2 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -24,11 +24,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.experimental.UseExperimental;
 import androidx.camera.core.Camera;
 import androidx.camera.core.CameraInfoUnavailableException;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraX;
 import androidx.camera.core.CameraXConfig;
+import androidx.camera.core.ExperimentalUseCaseGroup;
 import androidx.camera.core.ImageAnalysis;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.InitializationException;
@@ -246,6 +248,7 @@
     @SuppressWarnings("lambdaLast")
     @MainThread
     @NonNull
+    @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
     public Camera bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner,
             @NonNull CameraSelector cameraSelector,
             @NonNull UseCase... useCases) {
@@ -262,19 +265,18 @@
      *
      * <p> If one {@link UseCase} is in multiple {@link UseCaseGroup}s, it will be linked to
      * the {@link UseCaseGroup} in the latest
-     * {@code #bindToLifecycle(LifecycleOwner, CameraSelector, UseCaseGroup)} call.
-     *
-     * @hide
+     * {@link #bindToLifecycle(LifecycleOwner, CameraSelector, UseCaseGroup)} call.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @ExperimentalUseCaseGroupLifecycle
     @SuppressWarnings("lambdaLast")
     @MainThread
     @NonNull
+    @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
     public Camera bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner,
             @NonNull CameraSelector cameraSelector,
             @NonNull UseCaseGroup useCaseGroup) {
         return CameraX.bindToLifecycle(lifecycleOwner, cameraSelector,
-                useCaseGroup.getViewPort(), useCaseGroup.getUseCases());
+                useCaseGroup.getViewPort(), useCaseGroup.getUseCases().toArray(new UseCase[0]));
     }
 
     @Override
diff --git a/compose/compose-runtime/api/api_lint.ignore b/compose/compose-runtime/api/api_lint.ignore
index 37cdb9f..215da53 100644
--- a/compose/compose-runtime/api/api_lint.ignore
+++ b/compose/compose-runtime/api/api_lint.ignore
@@ -75,3 +75,8 @@
     Should avoid odd sized primitives; use `int` instead of `byte` in parameter value in androidx.compose.Composer.changed(byte value)
 NoByteOrShort: androidx.compose.Composer#changed(short) parameter #0:
     Should avoid odd sized primitives; use `int` instead of `short` in parameter value in androidx.compose.Composer.changed(short value)
+
+NotCloseable: androidx.compose.SlotReader:
+    Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.compose.SlotReader
+NotCloseable: androidx.compose.SlotWriter:
+    Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.compose.SlotWriter
diff --git a/concurrent/futures/api/api_lint.ignore b/concurrent/futures/api/api_lint.ignore
index 2539658..41ee2724 100644
--- a/concurrent/futures/api/api_lint.ignore
+++ b/concurrent/futures/api/api_lint.ignore
@@ -3,5 +3,9 @@
     Methods must not throw generic exceptions (`java.lang.Exception`)
 
 
+NotCloseable: androidx.concurrent.futures.CallbackToFutureAdapter.Completer:
+    Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class androidx.concurrent.futures.CallbackToFutureAdapter.Completer
+
+
 PairedRegistration: androidx.concurrent.futures.CallbackToFutureAdapter.Completer#addCancellationListener(Runnable, java.util.concurrent.Executor):
     Found addCancellationListener but not removeCancellationListener in androidx.concurrent.futures.CallbackToFutureAdapter.Completer
diff --git a/development/build_log_simplifier.py b/development/build_log_simplifier.py
index b3cceb7..b7f5cf2 100755
--- a/development/build_log_simplifier.py
+++ b/development/build_log_simplifier.py
@@ -69,6 +69,48 @@
             prev_line_is_boring = False
     return result
 
+def remove_known_uninteresting_lines(lines):
+  skipLines = {
+      "A fine-grained performance profile is available: use the --scan option.",
+      "* Get more help at https://help.gradle.org",
+      "Use '--warning-mode all' to show the individual deprecation warnings.",
+      "See https://docs.gradle.org/6.5/userguide/command_line_interface.html#sec:command_line_warnings"
+
+      "Note: Some input files use or override a deprecated API.",
+      "Note: Recompile with -Xlint:deprecation for details."
+  }
+  skipPrefixes = [
+      "See the profiling report at:",
+
+      "Deprecated Gradle features were used in this build"
+  ]
+  result = []
+  for line in lines:
+      stripped = line.strip()
+      if stripped in skipLines:
+          continue
+      include = True
+      for prefix in skipPrefixes:
+          if stripped.startswith(prefix):
+              include = False
+              break
+      if include:
+          result.append(line)
+  return result
+
+def collapse_consecutive_blank_lines(lines):
+    result = []
+    prev_blank = False
+    for line in lines:
+        if line.strip() == "":
+            if not prev_blank:
+                result.append(line)
+            prev_blank = True
+        else:
+            result.append(line)
+            prev_blank = False
+    return result
+
 # If multiple tasks have no output, this function removes all but the first and last
 # For example, turns this:
 #  > Task :a
@@ -79,13 +121,19 @@
 #  > Task :a
 #  > Task ...
 #  > Task :d
-def skip_tasks_having_no_output(lines):
+def collapse_tasks_having_no_output(lines):
     result = []
     pending_tasks = []
-    for line in lines + [""]:
+    for line in lines:
         is_task = line.startswith("> Task ")
         if is_task:
             pending_tasks.append(line)
+        elif line.strip() == "":
+            # If only blank lines occur between tasks, skip those blank lines
+            if len(pending_tasks) > 0:
+              pending_tasks.append(line)
+            else:
+              result.append(line)
         else:
             if len(pending_tasks) > 0:
                 result += pending_tasks[0]
@@ -94,8 +142,7 @@
                 if len(pending_tasks) > 1:
                     result += pending_tasks[-1]
                 pending_tasks = []
-            if line != "":
-                result.append(line)
+            result.append(line)
     return result
 
 try:
@@ -107,7 +154,9 @@
 
     lines = select_failing_task_output(lines)
     lines = shorten_uninteresting_stack_frames(lines)
-    lines = skip_tasks_having_no_output(lines)
+    lines = remove_known_uninteresting_lines(lines)
+    lines = collapse_consecutive_blank_lines(lines)
+    lines = collapse_tasks_having_no_output(lines)
 
     print(len(lines))
     print(''.join(lines))
diff --git a/development/build_log_simplifier.sh b/development/build_log_simplifier.sh
new file mode 100755
index 0000000..e2093bc
--- /dev/null
+++ b/development/build_log_simplifier.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+set -e
+
+usage() {
+  echo "usage: $0 <command> <arguments>"
+  echo
+  echo "executes <command> <arguments> and then runs build_log_simplifier.py against its output"
+  exit 1
+}
+
+if [[ "$1" == "" ]]; then
+  usage
+fi
+
+# run Gradle and save stdout and stderr into $logFile
+SCRIPT_PATH="$(cd $(dirname $0) && pwd)"
+if [ -n "$DIST_DIR" ]; then
+  LOG_DIR="$DIST_DIR"
+else
+  LOG_DIR="$SCRIPT_PATH/../../../out/dist"
+fi
+
+mkdir -p "$LOG_DIR"
+logFile="$LOG_DIR/gradle.log"
+rm -f "$logFile"
+echo "Running $@"
+if bash -c "$*" > >(tee -a "$logFile") 2> >(tee -a "$logFile" >&2); then
+  echo "Succeeded: $*"
+else
+  echo >&2
+  echo "Failed: $*" >&2
+  echo Attempting to locate the relevant error messages via build_log_simplifier.py >&2
+  echo >&2
+  # Try to identify the most relevant lines of output, and put them at the bottom of the
+  # output where they will also be placed into the build failure email.
+  # TODO: We may be able to stop cleaning up Gradle's output after Gradle can do this on its own:
+  # https://github.com/gradle/gradle/issues/1005
+  # and https://github.com/gradle/gradle/issues/13090
+  summaryLog="$LOG_DIR/error_summary.log"
+  $SCRIPT_PATH/build_log_simplifier.py $logFile | tail -n 100 | tee "$summaryLog" >&2
+  exit 1
+fi
diff --git a/gradlew b/gradlew
index 49facc3..db30cc3 100755
--- a/gradlew
+++ b/gradlew
@@ -240,49 +240,15 @@
   fi
 }
 
-# Runs a build and possibly modifies the output before displaying it, to make it easier to
-# interpret, and to make the relevant error messages fit inside of build failure emails.
-function runBuild() {
-  if [[ " ${@} " =~ " -Pandroidx.summarizeStderr " ]]; then
-    # run Gradle and save stdout and stderr into $logFile
-    if [ -n "$DIST_DIR" ]; then
-      LOG_DIR="$DIST_DIR"
-    else
-      LOG_DIR="$OUT_DIR/dist"
-    fi
-    mkdir -p "$LOG_DIR"
-    logFile="$LOG_DIR/gradle.log"
-    rm -f "$logFile"
-    if runGradle "$@" > >(tee -a "$logFile") 2> >(tee -a "$logFile" >&2); then
-      echo Gradle success
-    else
-      echo >&2
-      echo Gradle failure >&2
-      echo Attempting to locate the relevant error messages via build_log_simplifier.py >&2
-      echo >&2
-      # Try to identify the most relevant lines of output, and put them at the bottom of the
-      # output where they will also be placed into the build failure email.
-      # TODO: We may be able to stop cleaning up Gradle's output after Gradle can do this on its own:
-      # https://github.com/gradle/gradle/issues/1005
-      # and https://github.com/gradle/gradle/issues/13090
-      summaryLog="$LOG_DIR/error_summary.log"
-      $SCRIPT_PATH/development/build_log_simplifier.py $logFile | tail -n 100 | tee "$summaryLog" >&2
-      return 1
-    fi
-  else
-    runGradle "$@"
-  fi
-}
-
 if [[ " ${@} " =~ " -PdisallowExecution " ]]; then
   echo "Passing '-PdisallowExecution' directly is forbidden. Did you mean -PverifyUpToDate ?"
   echo "See TaskUpToDateValidator.java for more information"
   exit 1
 fi
 
-runBuild "$@"
+runGradle "$@"
 # Check whether we were given the "-PverifyUpToDate" argument
 if [[ " ${@} " =~ " -PverifyUpToDate " ]]; then
   # Re-run Gradle, and find all tasks that are unexpectly out of date
-  runBuild "$@" -PdisallowExecution --continue --info
+  runGradle "$@" -PdisallowExecution --continue --info
 fi
diff --git a/media/media/api/1.2.0-alpha03.txt b/media/media/api/1.2.0-alpha03.txt
index 1f73794..72d9f06 100644
--- a/media/media/api/1.2.0-alpha03.txt
+++ b/media/media/api/1.2.0-alpha03.txt
@@ -593,8 +593,8 @@
 
   public final class AudioManagerCompat {
     method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
-    method public static int getStreamMaxVolume(android.media.AudioManager, int);
-    method public static int getStreamMinVolume(android.media.AudioManager, int);
+    method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, int);
+    method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, int);
     method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
     field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
     field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
diff --git a/media/media/api/current.txt b/media/media/api/current.txt
index 1f73794..72d9f06 100644
--- a/media/media/api/current.txt
+++ b/media/media/api/current.txt
@@ -593,8 +593,8 @@
 
   public final class AudioManagerCompat {
     method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
-    method public static int getStreamMaxVolume(android.media.AudioManager, int);
-    method public static int getStreamMinVolume(android.media.AudioManager, int);
+    method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, int);
+    method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, int);
     method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
     field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
     field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
diff --git a/media/media/api/public_plus_experimental_1.2.0-alpha03.txt b/media/media/api/public_plus_experimental_1.2.0-alpha03.txt
index c845f56..db47a1b 100644
--- a/media/media/api/public_plus_experimental_1.2.0-alpha03.txt
+++ b/media/media/api/public_plus_experimental_1.2.0-alpha03.txt
@@ -593,8 +593,8 @@
 
   public final class AudioManagerCompat {
     method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
-    method public static int getStreamMaxVolume(android.media.AudioManager, int);
-    method public static int getStreamMinVolume(android.media.AudioManager, int);
+    method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
+    method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
     method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
     field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
     field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
diff --git a/media/media/api/public_plus_experimental_current.txt b/media/media/api/public_plus_experimental_current.txt
index c845f56..db47a1b 100644
--- a/media/media/api/public_plus_experimental_current.txt
+++ b/media/media/api/public_plus_experimental_current.txt
@@ -593,8 +593,8 @@
 
   public final class AudioManagerCompat {
     method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
-    method public static int getStreamMaxVolume(android.media.AudioManager, int);
-    method public static int getStreamMinVolume(android.media.AudioManager, int);
+    method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
+    method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
     method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
     field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
     field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
diff --git a/media/media/api/restricted_1.2.0-alpha03.txt b/media/media/api/restricted_1.2.0-alpha03.txt
index c14e757..37b65c8 100644
--- a/media/media/api/restricted_1.2.0-alpha03.txt
+++ b/media/media/api/restricted_1.2.0-alpha03.txt
@@ -616,8 +616,8 @@
 
   public final class AudioManagerCompat {
     method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
-    method public static int getStreamMaxVolume(android.media.AudioManager, int);
-    method public static int getStreamMinVolume(android.media.AudioManager, int);
+    method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
+    method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
     method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
     field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
     field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
@@ -680,9 +680,11 @@
 
   public abstract class VolumeProviderCompat {
     ctor public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int, String?);
     method public final int getCurrentVolume();
     method public final int getMaxVolume();
     method @androidx.media.VolumeProviderCompat.ControlType public final int getVolumeControl();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final String? getVolumeControlId();
     method public Object! getVolumeProvider();
     method public void onAdjustVolume(int);
     method public void onSetVolumeTo(int);
diff --git a/media/media/api/restricted_current.txt b/media/media/api/restricted_current.txt
index c14e757..37b65c8 100644
--- a/media/media/api/restricted_current.txt
+++ b/media/media/api/restricted_current.txt
@@ -616,8 +616,8 @@
 
   public final class AudioManagerCompat {
     method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
-    method public static int getStreamMaxVolume(android.media.AudioManager, int);
-    method public static int getStreamMinVolume(android.media.AudioManager, int);
+    method @IntRange(from=0) public static int getStreamMaxVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
+    method @IntRange(from=0) public static int getStreamMinVolume(android.media.AudioManager, @androidx.core.app.NotificationCompat.StreamType int);
     method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
     field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
     field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
@@ -680,9 +680,11 @@
 
   public abstract class VolumeProviderCompat {
     ctor public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public VolumeProviderCompat(@androidx.media.VolumeProviderCompat.ControlType int, int, int, String?);
     method public final int getCurrentVolume();
     method public final int getMaxVolume();
     method @androidx.media.VolumeProviderCompat.ControlType public final int getVolumeControl();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final String? getVolumeControlId();
     method public Object! getVolumeProvider();
     method public void onAdjustVolume(int);
     method public void onSetVolumeTo(int);
diff --git a/media/media/src/main/java/androidx/media/AudioManagerCompat.java b/media/media/src/main/java/androidx/media/AudioManagerCompat.java
index 6175267..a6d4ae7 100644
--- a/media/media/src/main/java/androidx/media/AudioManagerCompat.java
+++ b/media/media/src/main/java/androidx/media/AudioManagerCompat.java
@@ -19,7 +19,9 @@
 import android.media.AudioManager;
 import android.os.Build;
 
+import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat.StreamType;
 
 /** Compatibility library for {@link AudioManager} with fallbacks for older platforms. */
 public final class AudioManagerCompat {
@@ -127,7 +129,9 @@
      * @param streamType The stream type whose maximum volume index is returned.
      * @return The maximum valid volume index for the stream.
      */
-    public static int getStreamMaxVolume(@NonNull AudioManager audioManager, int streamType) {
+    @IntRange(from = 0)
+    public static int getStreamMaxVolume(@NonNull AudioManager audioManager,
+            @StreamType int streamType) {
         return audioManager.getStreamMaxVolume(streamType);
     }
 
@@ -137,7 +141,9 @@
      * @param streamType The stream type whose minimum volume index is returned.
      * @return The minimum valid volume index for the stream.
      */
-    public static int getStreamMinVolume(@NonNull AudioManager audioManager, int streamType) {
+    @IntRange(from = 0)
+    public static int getStreamMinVolume(@NonNull AudioManager audioManager,
+            @StreamType int streamType) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
             return audioManager.getStreamMinVolume(streamType);
         } else {
diff --git a/media/media/src/main/java/androidx/media/VolumeProviderCompat.java b/media/media/src/main/java/androidx/media/VolumeProviderCompat.java
index 6d835d6..201d74e 100644
--- a/media/media/src/main/java/androidx/media/VolumeProviderCompat.java
+++ b/media/media/src/main/java/androidx/media/VolumeProviderCompat.java
@@ -23,6 +23,7 @@
 import android.support.v4.media.session.MediaSessionCompat;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 
 import java.lang.annotation.Retention;
@@ -66,6 +67,7 @@
 
     private final int mControlType;
     private final int mMaxVolume;
+    private final String mControlId;
     private int mCurrentVolume;
     private Callback mCallback;
 
@@ -81,9 +83,26 @@
      * @param currentVolume The current volume.
      */
     public VolumeProviderCompat(@ControlType int volumeControl, int maxVolume, int currentVolume) {
+        this(volumeControl, maxVolume, currentVolume, null);
+    }
+
+    /**
+     * Create a new volume provider for handling volume events. You must specify
+     * the type of volume control and the maximum volume that can be used.
+     *
+     * @param volumeControl The method for controlling volume that is used by this provider.
+     * @param maxVolume The maximum allowed volume.
+     * @param currentVolume The current volume.
+     * @param volumeControlId The volume control ID of this provider.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    public VolumeProviderCompat(@ControlType int volumeControl, int maxVolume, int currentVolume,
+            @Nullable String volumeControlId) {
         mControlType = volumeControl;
         mMaxVolume = maxVolume;
         mCurrentVolume = currentVolume;
+        mControlId = volumeControlId;
     }
 
     /**
@@ -132,6 +151,19 @@
     }
 
     /**
+     * Gets the volume control ID. It can be used to identify which volume provider is
+     * used by the session.
+     *
+     * @return the volume control ID or {@code null} if it isn't set.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @Nullable
+    public final String getVolumeControlId() {
+        return mControlId;
+    }
+
+    /**
      * Override to handle requests to set the volume of the current output.
      *
      * @param volume The volume to set the output to.
@@ -166,18 +198,33 @@
      * @return An equivalent {@link android.media.VolumeProvider} object, or null if none.
      */
     public Object getVolumeProvider() {
-        if (mVolumeProviderFwk == null && Build.VERSION.SDK_INT >= 21) {
-            mVolumeProviderFwk = new VolumeProvider(mControlType, mMaxVolume, mCurrentVolume) {
-                @Override
-                public void onSetVolumeTo(int volume) {
-                    VolumeProviderCompat.this.onSetVolumeTo(volume);
-                }
+        if (mVolumeProviderFwk == null) {
+            if (Build.VERSION.SDK_INT >= 30) {
+                mVolumeProviderFwk = new VolumeProvider(mControlType, mMaxVolume, mCurrentVolume,
+                        mControlId) {
+                    @Override
+                    public void onSetVolumeTo(int volume) {
+                        VolumeProviderCompat.this.onSetVolumeTo(volume);
+                    }
 
-                @Override
-                public void onAdjustVolume(int direction) {
-                    VolumeProviderCompat.this.onAdjustVolume(direction);
-                }
-            };
+                    @Override
+                    public void onAdjustVolume(int direction) {
+                        VolumeProviderCompat.this.onAdjustVolume(direction);
+                    }
+                };
+            } else if (Build.VERSION.SDK_INT >= 21) {
+                mVolumeProviderFwk = new VolumeProvider(mControlType, mMaxVolume, mCurrentVolume) {
+                    @Override
+                    public void onSetVolumeTo(int volume) {
+                        VolumeProviderCompat.this.onSetVolumeTo(volume);
+                    }
+
+                    @Override
+                    public void onAdjustVolume(int direction) {
+                        VolumeProviderCompat.this.onAdjustVolume(direction);
+                    }
+                };
+            }
         }
         return mVolumeProviderFwk;
     }
diff --git a/media2/common/build.gradle b/media2/common/build.gradle
index a178d30..ab02530 100644
--- a/media2/common/build.gradle
+++ b/media2/common/build.gradle
@@ -9,7 +9,7 @@
 }
 
 dependencies {
-    api(project(":media:media"))
+    api("androidx.media:media:1.2.0-alpha03")
     api(GUAVA_LISTENABLE_FUTURE)
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
     implementation("androidx.collection:collection:1.0.0")
diff --git a/media2/player/build.gradle b/media2/player/build.gradle
index 52cad9b..3ef2668 100644
--- a/media2/player/build.gradle
+++ b/media2/player/build.gradle
@@ -10,7 +10,6 @@
 }
 
 dependencies {
-    api(project(":media:media"))
     api(project(":media2:media2-common"))
     api(GUAVA_LISTENABLE_FUTURE)
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
diff --git a/media2/session/build.gradle b/media2/session/build.gradle
index 0a6a8e8..ce7f481 100644
--- a/media2/session/build.gradle
+++ b/media2/session/build.gradle
@@ -10,7 +10,6 @@
 }
 
 dependencies {
-    api(project(":media:media"))
     api(project(":media2:media2-common"))
     api(GUAVA_LISTENABLE_FUTURE)
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
diff --git a/media2/widget/build.gradle b/media2/widget/build.gradle
index f17f115..e298d01 100644
--- a/media2/widget/build.gradle
+++ b/media2/widget/build.gradle
@@ -25,7 +25,6 @@
 }
 
 dependencies {
-    api(project(":media2:media2-common"))
     api(project(":media2:media2-session"))
     implementation("androidx.appcompat:appcompat:1.0.2")
     implementation("androidx.palette:palette:1.0.0")
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
index 23bc80e..aec35b5 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -29,18 +29,18 @@
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.yield
 import java.util.concurrent.CopyOnWriteArrayList
-import java.util.concurrent.atomic.AtomicBoolean
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 abstract class PagingDataDiffer<T : Any>(
     private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
 ) {
-    private val collecting = AtomicBoolean(false)
     private var presenter: PagePresenter<T> = PagePresenter.initial()
     private var receiver: UiReceiver? = null
     private val dataRefreshedListeners: MutableList<() -> Unit> = CopyOnWriteArrayList()
 
+    private val collectFromRunner = SingleRunner()
+
     @Volatile
     private var lastAccessedIndex: Int = 0
 
@@ -58,53 +58,47 @@
 
     open fun postEvents(): Boolean = false
 
-    suspend fun collectFrom(pagingData: PagingData<T>, callback: PresenterCallback) {
-        check(collecting.compareAndSet(false, true)) {
-            "Collecting from multiple PagingData concurrently is an illegal operation."
-        }
-
+    suspend fun collectFrom(
+        pagingData: PagingData<T>,
+        callback: PresenterCallback
+    ) = collectFromRunner.runInIsolation {
         receiver = pagingData.receiver
 
-        try {
-            pagingData.flow
-                .collect { event ->
-                    withContext(mainDispatcher) {
-                        if (event is PageEvent.Insert && event.loadType == REFRESH) {
-                            val newPresenter = PagePresenter(event)
-                            val transformedLastAccessedIndex = performDiff(
-                                previousList = presenter,
-                                newList = newPresenter,
-                                newCombinedLoadStates = event.combinedLoadStates,
-                                lastAccessedIndex = lastAccessedIndex
-                            )
-                            presenter = newPresenter
+        pagingData.flow.collect { event ->
+            withContext(mainDispatcher) {
+                if (event is PageEvent.Insert && event.loadType == REFRESH) {
+                    val newPresenter = PagePresenter(event)
+                    val transformedLastAccessedIndex = performDiff(
+                        previousList = presenter,
+                        newList = newPresenter,
+                        newCombinedLoadStates = event.combinedLoadStates,
+                        lastAccessedIndex = lastAccessedIndex
+                    )
+                    presenter = newPresenter
 
-                            // Dispatch ListUpdate as soon as we are done diffing.
-                            dataRefreshedListeners.forEach { listener -> listener() }
+                    // Dispatch ListUpdate as soon as we are done diffing.
+                    dataRefreshedListeners.forEach { listener -> listener() }
 
-                            // Transform the last loadAround index from the old list to the new list
-                            // by passing it through the DiffResult, and pass it forward as a
-                            // ViewportHint within the new list to the next generation of Pager.
-                            // This ensures prefetch distance for the last ViewportHint from the old
-                            // list is respected in the new list, even if invalidation interrupts
-                            // the prepend / append load that would have fulfilled it in the old
-                            // list.
-                            transformedLastAccessedIndex?.let { newIndex ->
-                                lastAccessedIndex = newIndex
-                                receiver?.addHint(presenter.loadAround(newIndex))
-                            }
-                        } else {
-                            if (postEvents()) {
-                                yield()
-                            }
-
-                            // Send event to presenter to be shown to the UI.
-                            presenter.processEvent(event, callback)
-                        }
+                    // Transform the last loadAround index from the old list to the new list
+                    // by passing it through the DiffResult, and pass it forward as a
+                    // ViewportHint within the new list to the next generation of Pager.
+                    // This ensures prefetch distance for the last ViewportHint from the old
+                    // list is respected in the new list, even if invalidation interrupts
+                    // the prepend / append load that would have fulfilled it in the old
+                    // list.
+                    transformedLastAccessedIndex?.let { newIndex ->
+                        lastAccessedIndex = newIndex
+                        receiver?.addHint(presenter.loadAround(newIndex))
                     }
+                } else {
+                    if (postEvents()) {
+                        yield()
+                    }
+
+                    // Send event to presenter to be shown to the UI.
+                    presenter.processEvent(event, callback)
                 }
-        } finally {
-            collecting.set(false)
+            }
         }
     }
 
diff --git a/paging/common/src/main/kotlin/androidx/paging/SingleRunner.kt b/paging/common/src/main/kotlin/androidx/paging/SingleRunner.kt
new file mode 100644
index 0000000..e575e6e
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/SingleRunner.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+/**
+ * Class which guarantees single execution of blocks passed to [runInIsolation] by cancelling the
+ * previous call. [runInIsolation] is backed by a [Mutex], which is fair, so concurrent callers of
+ * [runInIsolation] will trigger in order, with the last call winning (by cancelling previous calls)
+ */
+internal class SingleRunner {
+    private val mutex = Mutex()
+    private var previous: CoroutineScope? = null
+
+    suspend fun runInIsolation(block: suspend () -> Unit) {
+        coroutineScope {
+            mutex.withLock {
+                previous?.cancel()
+                previous = this
+            }
+            try {
+                block()
+            } finally {
+                mutex.withLock {
+                    if (previous == this) {
+                        previous = null
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
index 764f791..74f077e 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
@@ -45,8 +45,8 @@
 import org.junit.runners.JUnit4
 import kotlin.coroutines.ContinuationInterceptor
 import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
+import kotlin.test.assertTrue
 
 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalPagingApi::class)
 @RunWith(JUnit4::class)
@@ -123,14 +123,20 @@
     fun collectFrom_twiceConcurrently() = testScope.runBlockingTest {
         val differ = SimpleDiffer()
 
-        val job = launch {
-            differ.collectFrom(infinitelySuspendingPagingData(), dummyPresenterCallback)
-        }
-        assertFailsWith<IllegalStateException> {
+        val job1 = launch {
             differ.collectFrom(infinitelySuspendingPagingData(), dummyPresenterCallback)
         }
 
-        job.cancel()
+        // Ensure job1 is running.
+        assertTrue { job1.isActive }
+
+        val job2 = launch {
+            differ.collectFrom(infinitelySuspendingPagingData(), dummyPresenterCallback)
+        }
+
+        // job2 collection should cancel job1.
+        assertTrue { job1.isCancelled }
+        job2.cancel()
     }
 
     @Test
diff --git a/paging/common/src/test/kotlin/androidx/paging/SingleRunnerTest.kt b/paging/common/src/test/kotlin/androidx/paging/SingleRunnerTest.kt
new file mode 100644
index 0000000..d1ff12b9
--- /dev/null
+++ b/paging/common/src/test/kotlin/androidx/paging/SingleRunnerTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
+
+package androidx.paging
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(JUnit4::class)
+class SingleRunnerTest {
+    private val testScope = TestCoroutineScope()
+
+    @Test
+    fun cancelsPreviousRun() = runBlocking {
+        val runner = SingleRunner()
+        val job = launch(Dispatchers.Unconfined) {
+            runner.runInIsolation {
+                delay(Long.MAX_VALUE)
+            }
+        }
+
+        runner.runInIsolation {
+            // Immediately return.
+        }
+
+        assertTrue { job.isCancelled }
+    }
+
+    @Test
+    fun preventsCompletionUntilBlockCompletes() = testScope.runBlockingTest {
+        val runner = SingleRunner()
+        val job = testScope.launch {
+            runner.runInIsolation {
+                delay(1000)
+            }
+        }
+
+        advanceTimeBy(500)
+        assertFalse { job.isCompleted }
+
+        advanceTimeBy(500)
+        assertTrue { job.isCompleted }
+    }
+
+    @Test
+    fun orderedExecution() = testScope.runBlockingTest {
+        val jobStartList = mutableListOf<Int>()
+
+        val runner = SingleRunner()
+        for (index in 0..9) {
+            launch {
+                runner.runInIsolation {
+                    jobStartList.add(index)
+                    delay(Long.MAX_VALUE)
+                }
+            }
+        }
+
+        // Cancel previous job.
+        runner.runInIsolation {
+            // Immediately return.
+        }
+
+        assertEquals(List(10) { it }, jobStartList)
+    }
+}
\ No newline at end of file
diff --git a/paging/runtime/api/3.0.0-alpha02.txt b/paging/runtime/api/3.0.0-alpha02.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/3.0.0-alpha02.txt
+++ b/paging/runtime/api/3.0.0-alpha02.txt
@@ -24,7 +24,9 @@
   }
 
   public final class AsyncPagingDataDiffer<T> {
-    ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
     method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
 
   public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
     method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/api_lint.ignore b/paging/runtime/api/api_lint.ignore
index 45e4c89..22d1e0a 100644
--- a/paging/runtime/api/api_lint.ignore
+++ b/paging/runtime/api/api_lint.ignore
@@ -7,7 +7,9 @@
 
 ListenerLast: androidx.paging.AsyncPagedListDiffer#AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T>) parameter #1:
     Listeners should always be at end of argument list (method `AsyncPagedListDiffer`)
-
-
-TopLevelBuilder: androidx.paging.LivePagedListBuilder:
-    Builder should be defined as inner class: androidx.paging.LivePagedListBuilder
+ListenerLast: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher) parameter #2:
+    Listeners should always be at end of argument list (method `AsyncPagingDataDiffer`)
+ListenerLast: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher, kotlinx.coroutines.CoroutineDispatcher) parameter #2:
+    Listeners should always be at end of argument list (method `AsyncPagingDataDiffer`)
+ListenerLast: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher, kotlinx.coroutines.CoroutineDispatcher) parameter #3:
+    Listeners should always be at end of argument list (method `AsyncPagingDataDiffer`)
diff --git a/paging/runtime/api/current.txt b/paging/runtime/api/current.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/current.txt
+++ b/paging/runtime/api/current.txt
@@ -24,7 +24,9 @@
   }
 
   public final class AsyncPagingDataDiffer<T> {
-    ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
     method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
 
   public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
     method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/public_plus_experimental_3.0.0-alpha02.txt b/paging/runtime/api/public_plus_experimental_3.0.0-alpha02.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/public_plus_experimental_3.0.0-alpha02.txt
+++ b/paging/runtime/api/public_plus_experimental_3.0.0-alpha02.txt
@@ -24,7 +24,9 @@
   }
 
   public final class AsyncPagingDataDiffer<T> {
-    ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
     method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
 
   public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
     method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/public_plus_experimental_current.txt b/paging/runtime/api/public_plus_experimental_current.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/public_plus_experimental_current.txt
+++ b/paging/runtime/api/public_plus_experimental_current.txt
@@ -24,7 +24,9 @@
   }
 
   public final class AsyncPagingDataDiffer<T> {
-    ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
     method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
 
   public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
     method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/restricted_3.0.0-alpha02.txt b/paging/runtime/api/restricted_3.0.0-alpha02.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/restricted_3.0.0-alpha02.txt
+++ b/paging/runtime/api/restricted_3.0.0-alpha02.txt
@@ -24,7 +24,9 @@
   }
 
   public final class AsyncPagingDataDiffer<T> {
-    ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
     method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
 
   public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
     method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/api/restricted_current.txt b/paging/runtime/api/restricted_current.txt
index e8e80a2..33d883b 100644
--- a/paging/runtime/api/restricted_current.txt
+++ b/paging/runtime/api/restricted_current.txt
@@ -24,7 +24,9 @@
   }
 
   public final class AsyncPagingDataDiffer<T> {
-    ctor public AsyncPagingDataDiffer(kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
     method @androidx.paging.ExperimentalPagingApi public void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
@@ -99,6 +101,8 @@
 
   public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
     ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher, kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
     method @androidx.paging.ExperimentalPagingApi public final void addDataRefreshListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
     method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
     method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getDataRefreshFlow();
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
index 42ae07a..68b70f3 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
@@ -31,8 +31,10 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Runnable
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestCoroutineScope
 import kotlinx.coroutines.test.runBlockingTest
@@ -41,6 +43,8 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.CoroutineContext
+import kotlin.test.assertEquals
 import kotlin.test.assertTrue
 
 private class ListUpdateCapture : ListUpdateCallback {
@@ -348,4 +352,83 @@
             job2.cancel()
         }
     }
+
+    @Test
+    fun submitData_guaranteesOrder() = testScope.runBlockingTest {
+        val pager = Pager(config = PagingConfig(2, enablePlaceholders = false), initialKey = 50) {
+            TestPagingSource()
+        }
+
+        val reversedDispatcher = object : CoroutineDispatcher() {
+            var lastBlock: Runnable? = null
+            override fun dispatch(context: CoroutineContext, block: Runnable) {
+                // Save the first block to be dispatched, then run second one first after receiving
+                // calls to dispatch both.
+                val lastBlock = lastBlock
+                if (lastBlock == null) {
+                    this.lastBlock = block
+                } else {
+                    block.run()
+                    lastBlock.run()
+                }
+            }
+        }
+
+        val lifecycle = TestLifecycleOwner()
+        differ.submitData(lifecycle.lifecycle, PagingData.empty())
+        differ.submitData(lifecycle.lifecycle, pager.flow.first()) // Loads 6 items
+
+        // Ensure the second call wins when dispatched in order of execution.
+        advanceUntilIdle()
+        assertEquals(6, differ.itemCount)
+
+        val reversedLifecycle = TestLifecycleOwner(coroutineDispatcher = reversedDispatcher)
+        differ.submitData(reversedLifecycle.lifecycle, PagingData.empty())
+        differ.submitData(reversedLifecycle.lifecycle, pager.flow.first()) // Loads 6 items
+
+        // Ensure the second call wins when dispatched in reverse order of execution.
+        advanceUntilIdle()
+        assertEquals(6, differ.itemCount)
+    }
+
+    @Test
+    fun submitData_cancelsLastSuspendSubmit() = testScope.runBlockingTest {
+        pauseDispatcher {
+            val pager = Pager(
+                config = PagingConfig(2),
+                initialKey = 50
+            ) { TestPagingSource() }
+            val pager2 = Pager(
+                config = PagingConfig(2),
+                initialKey = 50
+            ) { TestPagingSource() }
+
+            val lifecycle = TestLifecycleOwner()
+            var jobSubmitted = false
+            val job = launch {
+                pager.flow.collectLatest {
+                    jobSubmitted = true
+                    differ.submitData(it)
+                }
+            }
+
+            advanceUntilIdle()
+
+            var job2Submitted = false
+            val job2 = launch {
+                pager2.flow.collectLatest {
+                    job2Submitted = true
+                    differ.submitData(lifecycle.lifecycle, it)
+                }
+            }
+
+            advanceUntilIdle()
+
+            assertTrue(jobSubmitted)
+            assertTrue(job2Submitted)
+
+            job.cancel()
+            job2.cancel()
+        }
+    }
 }
\ No newline at end of file
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
index cf3ce31..dd032e5 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
@@ -22,19 +22,16 @@
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.ListUpdateCallback
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.channels.ConflatedBroadcastChannel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.asFlow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.util.concurrent.CopyOnWriteArrayList
-import java.util.concurrent.atomic.AtomicReference
+import java.util.concurrent.atomic.AtomicInteger
 
 /**
  * Helper class for mapping a [PagingData] into a
@@ -44,11 +41,11 @@
  * [AsyncPagingDataDiffer] is exposed for complex cases, and where overriding [PagingDataAdapter] to
  * support paging isn't convenient.
  */
-class AsyncPagingDataDiffer<T : Any>(
-    private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
-    private val workerDispatcher: CoroutineDispatcher = Dispatchers.Default,
+class AsyncPagingDataDiffer<T : Any> @JvmOverloads constructor(
     private val diffCallback: DiffUtil.ItemCallback<T>,
-    private val updateCallback: ListUpdateCallback
+    private val updateCallback: ListUpdateCallback,
+    private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
+    private val workerDispatcher: CoroutineDispatcher = Dispatchers.Default
 ) {
     internal val callback = object : PresenterCallback {
         override fun onInserted(position: Int, count: Int) {
@@ -139,7 +136,7 @@
         }
     }
 
-    private val job = AtomicReference<Job?>(null)
+    private val submitDataId = AtomicInteger(0)
 
     /**
      * Present a [PagingData] until it is invalidated by a call to [refresh] or
@@ -163,11 +160,8 @@
      * @see [Pager]
      */
     suspend fun submitData(pagingData: PagingData<T>) {
-        try {
-            job.get()?.cancelAndJoin()
-        } finally {
-            differBase.collectFrom(pagingData, callback)
-        }
+        submitDataId.incrementAndGet()
+        differBase.collectFrom(pagingData, callback)
     }
 
     /**
@@ -183,16 +177,14 @@
      * @see [Pager]
      */
     fun submitData(lifecycle: Lifecycle, pagingData: PagingData<T>) {
-        var oldJob: Job?
-        var newJob: Job
-        do {
-            oldJob = job.get()
-            newJob = lifecycle.coroutineScope.launch(start = CoroutineStart.LAZY) {
-                oldJob?.cancelAndJoin()
+        val id = submitDataId.incrementAndGet()
+        lifecycle.coroutineScope.launch {
+            // Check id when this job runs to ensure the last synchronous call submitData always
+            // wins.
+            if (submitDataId.get() == id) {
                 differBase.collectFrom(pagingData, callback)
             }
-        } while (!job.compareAndSet(oldJob, newJob))
-        newJob.start()
+        }
     }
 
     /**
diff --git a/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt b/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
index 0baa40c..a6cbdd0 100644
--- a/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
+++ b/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
@@ -47,16 +47,16 @@
  *
  * @sample androidx.paging.samples.pagingDataAdapterSample
  */
-abstract class PagingDataAdapter<T : Any, VH : RecyclerView.ViewHolder>(
+abstract class PagingDataAdapter<T : Any, VH : RecyclerView.ViewHolder> @JvmOverloads constructor(
     diffCallback: DiffUtil.ItemCallback<T>,
     mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
     workerDispatcher: CoroutineDispatcher = Dispatchers.Default
 ) : RecyclerView.Adapter<VH>() {
     private val differ = AsyncPagingDataDiffer(
-        mainDispatcher = mainDispatcher,
-        workerDispatcher = workerDispatcher,
         diffCallback = diffCallback,
-        updateCallback = AdapterListUpdateCallback(this)
+        updateCallback = AdapterListUpdateCallback(this),
+        mainDispatcher = mainDispatcher,
+        workerDispatcher = workerDispatcher
     )
 
     /**
@@ -170,6 +170,7 @@
      * @param listener [LoadStates] listener to receive updates.
      *
      * @see removeLoadStateListener
+     * @sample androidx.paging.samples.addLoadStateListenerSample
      */
     fun addLoadStateListener(listener: (CombinedLoadStates) -> Unit) {
         differ.addLoadStateListener(listener)
diff --git a/transition/transition/build.gradle b/transition/transition/build.gradle
index 7a5e169..97f1428 100644
--- a/transition/transition/build.gradle
+++ b/transition/transition/build.gradle
@@ -14,10 +14,8 @@
     api("androidx.annotation:annotation:1.1.0")
     api("androidx.core:core:1.1.0")
     implementation("androidx.collection:collection:1.1.0")
-    compileOnly(project(":fragment:fragment"))
+    compileOnly("androidx.fragment:fragment:1.2.5")
     compileOnly("androidx.appcompat:appcompat:1.0.1")
-    // necessary for IJ to resolve dependencies.
-    implementation(project(":lifecycle:lifecycle-runtime"))
 
     androidTestImplementation(KOTLIN_STDLIB)
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/autofill/AndroidAutofillBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/autofill/AndroidAutofillBenchmark.kt
index 1417f3d..ec407be 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/autofill/AndroidAutofillBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/autofill/AndroidAutofillBenchmark.kt
@@ -16,7 +16,6 @@
 
 package androidx.ui.benchmark.test.autofill
 
-import android.graphics.Rect
 import android.util.SparseArray
 import android.view.View
 import android.view.autofill.AutofillValue
@@ -24,12 +23,13 @@
 import androidx.benchmark.junit4.measureRepeated
 import androidx.test.annotation.UiThreadTest
 import androidx.test.filters.LargeTest
-import androidx.ui.autofill.AutofillNode
 import androidx.test.filters.SdkSuppress
+import androidx.ui.autofill.AutofillNode
 import androidx.ui.autofill.AutofillTree
 import androidx.ui.autofill.AutofillType
 import androidx.ui.core.AutofillTreeAmbient
 import androidx.ui.core.ViewAmbient
+import androidx.ui.geometry.Rect
 import androidx.ui.test.createComposeRule
 import org.junit.Before
 import org.junit.Rule
@@ -67,7 +67,7 @@
         val autofillNode = AutofillNode(
             onFill = {},
             autofillTypes = listOf(AutofillType.PersonFullName),
-            boundingBox = Rect(0, 0, 0, 0)
+            boundingBox = Rect(0f, 0f, 0f, 0f)
         )
         val autofillValues = SparseArray<AutofillValue>().apply {
             append(autofillNode.id, AutofillValue.forText("Name"))
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/input/EditProcessorBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/input/EditProcessorBenchmark.kt
index a5f96bd..e73d55d 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/input/EditProcessorBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/input/EditProcessorBenchmark.kt
@@ -81,7 +81,7 @@
                     onNewState(
                         TextFieldValue(
                             text = initText.text,
-                            selection = TextRange(5, 5)
+                            selection = TextRange(5)
                         ),
                         null, // text input service, not used.
                         0 // session token, not used
diff --git a/ui/ui-animation-core/api/api_lint.ignore b/ui/ui-animation-core/api/api_lint.ignore
index 3d76ee0..35e0da1 100644
--- a/ui/ui-animation-core/api/api_lint.ignore
+++ b/ui/ui-animation-core/api/api_lint.ignore
@@ -41,5 +41,9 @@
     Missing nullability on method `getValue` return
 
 
+NotCloseable: androidx.animation.BaseAnimatedValue:
+    Classes that release resources (stop()) should implement AutoClosable and CloseGuard: class androidx.animation.BaseAnimatedValue
+
+
 TopLevelBuilder: androidx.animation.AnimationBuilder:
     Builder should be defined as inner class: androidx.animation.AnimationBuilder
diff --git a/ui/ui-core/api/0.1.0-dev14.txt b/ui/ui-core/api/0.1.0-dev14.txt
index a640771..db9cb26 100644
--- a/ui/ui-core/api/0.1.0-dev14.txt
+++ b/ui/ui-core/api/0.1.0-dev14.txt
@@ -24,16 +24,16 @@
   }
 
   public final class AutofillNode {
-    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> component1();
-    method public android.graphics.Rect? component2();
+    method public androidx.ui.geometry.Rect? component2();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
-    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
-    method public android.graphics.Rect? getBoundingBox();
+    method public androidx.ui.geometry.Rect? getBoundingBox();
     method public int getId();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
-    method public void setBoundingBox(android.graphics.Rect? p);
+    method public void setBoundingBox(androidx.ui.geometry.Rect? p);
     property public final int id;
   }
 
diff --git a/ui/ui-core/api/api_lint.ignore b/ui/ui-core/api/api_lint.ignore
index fbd03c6..83fe9f6 100644
--- a/ui/ui-core/api/api_lint.ignore
+++ b/ui/ui-core/api/api_lint.ignore
@@ -59,5 +59,9 @@
     Missing nullability on method `toString-impl` return
 
 
+NotCloseable: androidx.ui.core.OwnedLayer:
+    Classes that release resources (destroy()) should implement AutoClosable and CloseGuard: class androidx.ui.core.OwnedLayer
+
+
 TopLevelBuilder: androidx.ui.graphics.vector.VectorAssetBuilder:
     Builder should be defined as inner class: androidx.ui.graphics.vector.VectorAssetBuilder
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index a640771..db9cb26 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -24,16 +24,16 @@
   }
 
   public final class AutofillNode {
-    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> component1();
-    method public android.graphics.Rect? component2();
+    method public androidx.ui.geometry.Rect? component2();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
-    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
-    method public android.graphics.Rect? getBoundingBox();
+    method public androidx.ui.geometry.Rect? getBoundingBox();
     method public int getId();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
-    method public void setBoundingBox(android.graphics.Rect? p);
+    method public void setBoundingBox(androidx.ui.geometry.Rect? p);
     property public final int id;
   }
 
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev14.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev14.txt
index 3896319..bfbb96e 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev14.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev14.txt
@@ -24,16 +24,16 @@
   }
 
   public final class AutofillNode {
-    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> component1();
-    method public android.graphics.Rect? component2();
+    method public androidx.ui.geometry.Rect? component2();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
-    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
-    method public android.graphics.Rect? getBoundingBox();
+    method public androidx.ui.geometry.Rect? getBoundingBox();
     method public int getId();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
-    method public void setBoundingBox(android.graphics.Rect? p);
+    method public void setBoundingBox(androidx.ui.geometry.Rect? p);
     property public final int id;
   }
 
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index 3896319..bfbb96e 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -24,16 +24,16 @@
   }
 
   public final class AutofillNode {
-    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> component1();
-    method public android.graphics.Rect? component2();
+    method public androidx.ui.geometry.Rect? component2();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
-    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
-    method public android.graphics.Rect? getBoundingBox();
+    method public androidx.ui.geometry.Rect? getBoundingBox();
     method public int getId();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
-    method public void setBoundingBox(android.graphics.Rect? p);
+    method public void setBoundingBox(androidx.ui.geometry.Rect? p);
     property public final int id;
   }
 
diff --git a/ui/ui-core/api/restricted_0.1.0-dev14.txt b/ui/ui-core/api/restricted_0.1.0-dev14.txt
index 9b29c0d..98633c67 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev14.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev14.txt
@@ -24,16 +24,16 @@
   }
 
   public final class AutofillNode {
-    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> component1();
-    method public android.graphics.Rect? component2();
+    method public androidx.ui.geometry.Rect? component2();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
-    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
-    method public android.graphics.Rect? getBoundingBox();
+    method public androidx.ui.geometry.Rect? getBoundingBox();
     method public int getId();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
-    method public void setBoundingBox(android.graphics.Rect? p);
+    method public void setBoundingBox(androidx.ui.geometry.Rect? p);
     property public final int id;
   }
 
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 9b29c0d..98633c67 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -24,16 +24,16 @@
   }
 
   public final class AutofillNode {
-    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> component1();
-    method public android.graphics.Rect? component2();
+    method public androidx.ui.geometry.Rect? component2();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
-    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, androidx.ui.geometry.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
     method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
-    method public android.graphics.Rect? getBoundingBox();
+    method public androidx.ui.geometry.Rect? getBoundingBox();
     method public int getId();
     method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
-    method public void setBoundingBox(android.graphics.Rect? p);
+    method public void setBoundingBox(androidx.ui.geometry.Rect? p);
     property public final int id;
   }
 
diff --git a/ui/ui-core/build.gradle b/ui/ui-core/build.gradle
index 1a54985..2111805 100644
--- a/ui/ui-core/build.gradle
+++ b/ui/ui-core/build.gradle
@@ -40,6 +40,7 @@
             implementation(KOTLIN_COROUTINES_CORE)
 
             api project(":ui:ui-unit")
+            api project(":ui:ui-geometry")
             api project(":ui:ui-graphics")
             api project(":ui:ui-text-core")
             api project(":ui:ui-animation-core")
diff --git a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/autofill/ExplicitAutofillTypesDemo.kt b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/autofill/ExplicitAutofillTypesDemo.kt
index ad62c59..28573ff 100644
--- a/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/autofill/ExplicitAutofillTypesDemo.kt
+++ b/ui/ui-core/integration-tests/ui-core-demos/src/main/java/androidx/ui/core/demos/autofill/ExplicitAutofillTypesDemo.kt
@@ -27,6 +27,7 @@
 import androidx.ui.core.Modifier
 import androidx.ui.core.PassThroughLayout
 import androidx.ui.core.onChildPositioned
+import androidx.ui.core.toComposeRect
 import androidx.ui.foundation.Text
 import androidx.ui.foundation.TextField
 import androidx.ui.input.ImeAction
@@ -106,7 +107,9 @@
     autofillTree += autofillNode
 
     @Suppress("DEPRECATION")
-    PassThroughLayout(onChildPositioned { autofillNode.boundingBox = it.boundingBox() }) {
+    PassThroughLayout(onChildPositioned {
+        autofillNode.boundingBox = it.boundingBox().toComposeRect()
+    }) {
         children(autofillNode)
     }
 }
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/AndroidAutoFillTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/AndroidAutoFillTest.kt
index a000527..4ab15c5 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/AndroidAutoFillTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/core/AndroidAutoFillTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.ui.core
 
-import android.graphics.Rect
 import android.util.SparseArray
 import android.view.View
 import android.view.ViewStructure
@@ -29,6 +28,7 @@
 import androidx.ui.autofill.AutofillNode
 import androidx.ui.autofill.AutofillTree
 import androidx.ui.autofill.AutofillType
+import androidx.ui.geometry.Rect
 import androidx.ui.test.android.fake.FakeViewStructure
 import androidx.ui.test.createComposeRule
 import com.google.common.truth.Truth.assertThat
@@ -83,7 +83,7 @@
         val autofillNode = AutofillNode(
             onFill = {},
             autofillTypes = listOf(AutofillType.PersonFullName),
-            boundingBox = Rect(0, 0, 0, 0)
+            boundingBox = Rect(0f, 0f, 0f, 0f)
         )
         autofillTree += autofillNode
 
@@ -112,7 +112,7 @@
         val autofillNode = AutofillNode(
             onFill = { autofilledValue = it },
             autofillTypes = listOf(AutofillType.PersonFullName),
-            boundingBox = Rect(0, 0, 0, 0)
+            boundingBox = Rect(0f, 0f, 0f, 0f)
         )
         val autofillValues = SparseArray<AutofillValue>().apply {
             append(autofillNode.id, AutofillValue.forText(expectedValue))
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/input/RecordingInputConnectionTest.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/input/RecordingInputConnectionTest.kt
index bb6be1d..df06c1f 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/input/RecordingInputConnectionTest.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/ui/input/RecordingInputConnectionTest.kt
@@ -45,7 +45,7 @@
         ic = RecordingInputConnection(
             TextFieldValue(
                 "",
-                TextRange(0, 0)
+                TextRange(0)
             ), listener
         )
     }
@@ -58,7 +58,7 @@
         // Set "Hello, World", and place the cursor at the beginning of the text.
         ic.mTextFieldValue = TextFieldValue(
             text = "Hello, World",
-            selection = TextRange(0, 0))
+            selection = TextRange(0))
 
         assertEquals("", ic.getTextBeforeCursor(100, 0))
         assertEquals("Hello, World", ic.getTextAfterCursor(100, 0))
@@ -66,7 +66,7 @@
         // Set "Hello, World", and place the cursor between "H" and "e".
         ic.mTextFieldValue = TextFieldValue(
             text = "Hello, World",
-            selection = TextRange(1, 1))
+            selection = TextRange(1))
 
         assertEquals("H", ic.getTextBeforeCursor(100, 0))
         assertEquals("ello, World", ic.getTextAfterCursor(100, 0))
@@ -74,7 +74,7 @@
         // Set "Hello, World", and place the cursor at the end of the text.
         ic.mTextFieldValue = TextFieldValue(
             text = "Hello, World",
-            selection = TextRange(12, 12))
+            selection = TextRange(12))
 
         assertEquals("Hello, World", ic.getTextBeforeCursor(100, 0))
         assertEquals("", ic.getTextAfterCursor(100, 0))
@@ -85,7 +85,7 @@
         // Set "Hello, World", and place the cursor at the beginning of the text.
         ic.mTextFieldValue = TextFieldValue(
             text = "Hello, World",
-            selection = TextRange(0, 0))
+            selection = TextRange(0))
 
         assertEquals("", ic.getTextBeforeCursor(5, 0))
         assertEquals("Hello", ic.getTextAfterCursor(5, 0))
@@ -93,7 +93,7 @@
         // Set "Hello, World", and place the cursor between "H" and "e".
         ic.mTextFieldValue = TextFieldValue(
             text = "Hello, World",
-            selection = TextRange(1, 1))
+            selection = TextRange(1))
 
         assertEquals("H", ic.getTextBeforeCursor(5, 0))
         assertEquals("ello,", ic.getTextAfterCursor(5, 0))
@@ -101,7 +101,7 @@
         // Set "Hello, World", and place the cursor at the end of the text.
         ic.mTextFieldValue = TextFieldValue(
             text = "Hello, World",
-            selection = TextRange(12, 12))
+            selection = TextRange(12))
 
         assertEquals("World", ic.getTextBeforeCursor(5, 0))
         assertEquals("", ic.getTextAfterCursor(5, 0))
@@ -112,7 +112,7 @@
         // Set "Hello, World", and place the cursor at the beginning of the text.
         ic.mTextFieldValue = TextFieldValue(
             text = "Hello, World",
-            selection = TextRange(0, 0))
+            selection = TextRange(0))
 
         assertEquals("", ic.getSelectedText(0))
 
@@ -135,7 +135,7 @@
     fun commitTextTest() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
 
         // Inserting "Hello, " into the empty text field.
         assertTrue(ic.commitText("Hello, ", 1))
@@ -150,7 +150,7 @@
     fun commitTextTest_batchSession() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
 
         // IME set text "Hello, World." with two commitText API within the single batch session.
         // Do not callback to listener during batch session.
@@ -175,7 +175,7 @@
     fun setComposingRegion() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
 
         // Mark first "H" as composition.
         assertTrue(ic.setComposingRegion(0, 1))
@@ -190,7 +190,7 @@
     fun setComposingRegion_batchSession() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
 
         // Do not callback to listener during batch session.
         ic.beginBatchEdit()
@@ -214,7 +214,7 @@
     fun setComposingTextTest() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
 
         // Inserting "Hello, " into the empty text field.
         assertTrue(ic.setComposingText("Hello, ", 1))
@@ -229,7 +229,7 @@
     fun setComposingTextTest_batchSession() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
 
         // IME set text "Hello, World." with two setComposingText API within the single batch
         // session. Do not callback to listener during batch session.
@@ -254,7 +254,7 @@
     fun deleteSurroundingText() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
 
         // Delete first "Hello, " characters
         assertTrue(ic.deleteSurroundingText(0, 6))
@@ -269,7 +269,7 @@
     fun deleteSurroundingText_batchSession() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
 
         // Do not callback to listener during batch session.
         ic.beginBatchEdit()
@@ -293,7 +293,7 @@
     fun deleteSurroundingTextInCodePoints() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
 
         // Delete first "Hello, " characters
         assertTrue(ic.deleteSurroundingTextInCodePoints(0, 6))
@@ -308,7 +308,7 @@
     fun deleteSurroundingTextInCodePoints_batchSession() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
 
         // Do not callback to listener during batch session.
         ic.beginBatchEdit()
@@ -332,7 +332,7 @@
     fun setSelection() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
 
         // Select "Hello, "
         assertTrue(ic.setSelection(0, 6))
@@ -347,7 +347,7 @@
     fun setSelection_batchSession() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
 
         // Do not callback to listener during batch session.
         ic.beginBatchEdit()
@@ -371,7 +371,7 @@
     fun finishComposingText() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
 
         // Cancel any ongoing composition. In this example, there is no composition range, but
         // should record the API call
@@ -387,7 +387,7 @@
     fun finishComposingText_batchSession() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "Hello, World", selection = TextRange(0))
 
         // Do not callback to listener during batch session.
         ic.beginBatchEdit()
@@ -411,7 +411,7 @@
     fun mixedAPICalls_batchSession() {
         val captor = argumentCaptor<List<EditOperation>>()
 
-        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0, 0))
+        ic.mTextFieldValue = TextFieldValue(text = "", selection = TextRange(0))
 
         // Do not callback to listener during batch session.
         ic.beginBatchEdit()
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AndroidAutofill.kt b/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AndroidAutofill.kt
index b6b38a3..6d650d4 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AndroidAutofill.kt
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AndroidAutofill.kt
@@ -24,6 +24,7 @@
 import android.view.autofill.AutofillManager
 import android.view.autofill.AutofillValue
 import androidx.annotation.RequiresApi
+import androidx.ui.core.toAndroidRect
 
 /**
  * Autofill implementation for Android.
@@ -45,7 +46,8 @@
         autofillManager.notifyViewEntered(
             view,
             autofillNode.id,
-            autofillNode.boundingBox ?: error("requestAutofill called before onChildPositioned()")
+            autofillNode.boundingBox?.toAndroidRect()
+                ?: error("requestAutofill called before onChildPositioned()")
         )
     }
 
@@ -79,11 +81,12 @@
                 // null, the autofill overlay will not be shown.
                 Log.w(
                     "Autofill Warning",
-                    """Bounding box not set. 
+                    """Bounding box not set.
                         Did you call perform autofillTree before the component was positioned? """
                 )
             }
-            autofillNode.boundingBox?.run { setDimens(left, top, 0, 0, width(), height()) }
+            autofillNode.boundingBox?.toAndroidRect()?.run {
+                setDimens(left, top, 0, 0, width(), height()) }
         }
         index++
     }
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/input/TextInputServiceAndroid.kt b/ui/ui-core/src/androidMain/kotlin/androidx/ui/input/TextInputServiceAndroid.kt
index cb8003e..059d6ee 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/ui/input/TextInputServiceAndroid.kt
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/ui/input/TextInputServiceAndroid.kt
@@ -40,7 +40,7 @@
     private var onEditCommand: (List<EditOperation>) -> Unit = {}
     private var onImeActionPerformed: (ImeAction) -> Unit = {}
 
-    private var state = TextFieldValue(text = "", selection = TextRange(0, 0))
+    private var state = TextFieldValue(text = "", selection = TextRange(0))
     private var keyboardType = KeyboardType.Text
     private var imeAction = ImeAction.Unspecified
     private var ic: RecordingInputConnection? = null
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/Autofill.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/Autofill.kt
similarity index 94%
rename from ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/Autofill.kt
rename to ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/Autofill.kt
index c11fd61..cf57171 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/Autofill.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/Autofill.kt
@@ -16,8 +16,8 @@
 
 package androidx.ui.autofill
 
-import android.graphics.Rect
-import androidx.annotation.GuardedBy
+import androidx.ui.geometry.Rect
+import androidx.ui.util.annotation.GuardedBy
 
 /**
  * Autofill API.
@@ -75,8 +75,8 @@
         @GuardedBy("this")
         private var previousId = 0
 
-        @Synchronized
-        private fun generateId() = ++previousId
+        private fun generateId() =
+            synchronized(this) { ++previousId }
     }
 
     val id: Int = generateId()
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AutofillTree.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/AutofillTree.kt
similarity index 100%
rename from ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AutofillTree.kt
rename to ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/AutofillTree.kt
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AutofillType.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/AutofillType.kt
similarity index 100%
rename from ui/ui-core/src/androidMain/kotlin/androidx/ui/autofill/AutofillType.kt
rename to ui/ui-core/src/commonMain/kotlin/androidx/ui/autofill/AutofillType.kt
diff --git a/ui/ui-core/src/unitTest/kotlin/androidx/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt b/ui/ui-core/src/unitTest/kotlin/androidx/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt
index 0081b3f..77550bc8 100644
--- a/ui/ui-core/src/unitTest/kotlin/androidx/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt
+++ b/ui/ui-core/src/unitTest/kotlin/androidx/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt
@@ -48,7 +48,7 @@
         ic = RecordingInputConnection(
             TextFieldValue(
                 "",
-                TextRange(0, 0)
+                TextRange(0)
             ), listener
         )
     }
@@ -58,7 +58,7 @@
         val imm: InputMethodManager = mock()
         val view: View = mock()
 
-        val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+        val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
 
         ic.updateInputState(inputState, imm, view)
 
@@ -73,7 +73,7 @@
 
         ic.getExtractedText(null, InputConnection.GET_EXTRACTED_TEXT_MONITOR)
 
-        val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange(0, 0))
+        val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange(0))
 
         ic.updateInputState(inputState, imm, view)
 
diff --git a/ui/ui-desktop/README.md b/ui/ui-desktop/README.md
index c266f16..f829147 100644
--- a/ui/ui-desktop/README.md
+++ b/ui/ui-desktop/README.md
@@ -2,4 +2,4 @@
 
 To try:
 
-    ./gradlew -Pcompose.desktop=1 :ui:ui-desktop:samples:run
\ No newline at end of file
+    ./gradlew :ui:ui-desktop:samples:run
diff --git a/ui/ui-desktop/android-emu/src/desktopMain/kotlin/android/graphics/Path.kt b/ui/ui-desktop/android-emu/src/desktopMain/kotlin/android/graphics/Path.kt
index cf5b361..42216bb 100644
--- a/ui/ui-desktop/android-emu/src/desktopMain/kotlin/android/graphics/Path.kt
+++ b/ui/ui-desktop/android-emu/src/desktopMain/kotlin/android/graphics/Path.kt
@@ -35,9 +35,8 @@
         ry: Float,
         dir: Direction
     ) {
-        println("Path.addRoundRect")
-        // TODO: incorrect.
-        skijaPath.addPoly(floatArrayOf(left, top, left, bottom, right, bottom, right, top), true)
+        skijaPath.addRoundedRect(org.jetbrains.skija.RoundedRect.makeLTRB(
+            left, top, right, bottom, rx, ry), dir.skija)
     }
 
     fun reset() {
@@ -49,20 +48,9 @@
         radii: FloatArray,
         dir: Direction
     ) {
-        println("Path.addRoundRect")
-        // TODO: incorrect.
-        skijaPath.addPoly(floatArrayOf(
-            rect.left,
-            rect.top,
-            rect.left,
-            rect.bottom,
-            rect.right,
-            rect.bottom,
-            rect.right,
-            rect.top
-        ), true)
+        skijaPath.addRoundedRect(org.jetbrains.skija.RoundedRect.makeComplexLTRB(
+            rect.left, rect.top, rect.right, rect.bottom, radii), dir.skija)
     }
 
-    // TODO: incorrect
-    fun isConvex(): Boolean = true
+    fun isConvex(): Boolean = skijaPath.isConvex
 }
\ No newline at end of file
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/TextFieldOnValueChangeTextFieldValueTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/TextFieldOnValueChangeTextFieldValueTest.kt
index bb904a6..dbb0db9 100644
--- a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/TextFieldOnValueChangeTextFieldValueTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/TextFieldOnValueChangeTextFieldValueTest.kt
@@ -78,7 +78,7 @@
                 val state = state {
                     androidx.ui.input.TextFieldValue(
                         "abcde",
-                        TextRange(0, 0)
+                        TextRange(0)
                     )
                 }
                 TextField(
@@ -128,7 +128,7 @@
                 .invoke(eq(
                     androidx.ui.input.TextFieldValue(
                         "ABCDEabcde",
-                        TextRange(5, 5)
+                        TextRange(5)
                     )
                 ))
         }
@@ -152,7 +152,7 @@
                 .invoke(eq(
                     androidx.ui.input.TextFieldValue(
                         text = "ABCDEabcde",
-                        selection = TextRange(5, 5),
+                        selection = TextRange(5),
                         composition = TextRange(0, composingText.length)
                     )
                 ))
@@ -167,7 +167,7 @@
             verify(onValueChange, times(1)).invoke(eq(
                 androidx.ui.input.TextFieldValue(
                     "abcde",
-                    TextRange(1, 1)
+                    TextRange(1)
                 )
             ))
         }
@@ -182,7 +182,7 @@
                 .invoke(eq(
                     androidx.ui.input.TextFieldValue(
                         text = "ABCDEabcde",
-                        selection = TextRange(5, 5),
+                        selection = TextRange(5),
                         composition = TextRange(0, composingText.length)
                     )
                 ))
@@ -201,7 +201,7 @@
             verify(onValueChange, times(1)).invoke(eq(
                 androidx.ui.input.TextFieldValue(
                     "bcde",
-                    TextRange(0, 0)
+                    TextRange(0)
                 )
             ))
         }
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/TextField.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/TextField.kt
index 72bf9a5..0d93d9d 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/TextField.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/TextField.kt
@@ -85,7 +85,7 @@
     @Stable
     val text: String = "",
     @Stable
-    val selection: TextRange = TextRange(0, 0)
+    val selection: TextRange = TextRange(0)
 ) {
     companion object {
         /**
diff --git a/ui/ui-graphics/api/0.1.0-dev14.txt b/ui/ui-graphics/api/0.1.0-dev14.txt
index 5685197..58265da 100644
--- a/ui/ui-graphics/api/0.1.0-dev14.txt
+++ b/ui/ui-graphics/api/0.1.0-dev14.txt
@@ -4,6 +4,7 @@
   public final class RectHelperKt {
     method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
     method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+    method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
   }
 
 }
diff --git a/ui/ui-graphics/api/api_lint.ignore b/ui/ui-graphics/api/api_lint.ignore
index e82ea03..2733c6a 100644
--- a/ui/ui-graphics/api/api_lint.ignore
+++ b/ui/ui-graphics/api/api_lint.ignore
@@ -101,5 +101,13 @@
     Missing nullability on parameter `p` in method `toString-impl`
 
 
+NotCloseable: androidx.ui.graphics.AndroidPath:
+    Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.ui.graphics.AndroidPath
+NotCloseable: androidx.ui.graphics.Path:
+    Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.ui.graphics.Path
+NotCloseable: androidx.ui.graphics.vector.PathBuilder:
+    Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.ui.graphics.vector.PathBuilder
+
+
 TopLevelBuilder: androidx.ui.graphics.vector.PathBuilder:
     Builder should be defined as inner class: androidx.ui.graphics.vector.PathBuilder
diff --git a/ui/ui-graphics/api/current.txt b/ui/ui-graphics/api/current.txt
index 5685197..58265da 100644
--- a/ui/ui-graphics/api/current.txt
+++ b/ui/ui-graphics/api/current.txt
@@ -4,6 +4,7 @@
   public final class RectHelperKt {
     method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
     method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+    method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
   }
 
 }
diff --git a/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev14.txt b/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev14.txt
index 5685197..58265da 100644
--- a/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev14.txt
+++ b/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev14.txt
@@ -4,6 +4,7 @@
   public final class RectHelperKt {
     method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
     method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+    method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
   }
 
 }
diff --git a/ui/ui-graphics/api/public_plus_experimental_current.txt b/ui/ui-graphics/api/public_plus_experimental_current.txt
index 5685197..58265da 100644
--- a/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -4,6 +4,7 @@
   public final class RectHelperKt {
     method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
     method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+    method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
   }
 
 }
diff --git a/ui/ui-graphics/api/restricted_0.1.0-dev14.txt b/ui/ui-graphics/api/restricted_0.1.0-dev14.txt
index cc358bd..7c2c958 100644
--- a/ui/ui-graphics/api/restricted_0.1.0-dev14.txt
+++ b/ui/ui-graphics/api/restricted_0.1.0-dev14.txt
@@ -4,6 +4,7 @@
   public final class RectHelperKt {
     method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
     method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+    method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
   }
 
 }
diff --git a/ui/ui-graphics/api/restricted_current.txt b/ui/ui-graphics/api/restricted_current.txt
index cc358bd..7c2c958 100644
--- a/ui/ui-graphics/api/restricted_current.txt
+++ b/ui/ui-graphics/api/restricted_current.txt
@@ -4,6 +4,7 @@
   public final class RectHelperKt {
     method public static android.graphics.Rect toAndroidRect(androidx.ui.geometry.Rect);
     method public static android.graphics.RectF toAndroidRectF(androidx.ui.geometry.Rect);
+    method public static androidx.ui.geometry.Rect toComposeRect(android.graphics.Rect);
   }
 
 }
diff --git a/ui/ui-graphics/src/androidMain/kotlin/androidx/ui/graphics/RectHelper.kt b/ui/ui-graphics/src/androidMain/kotlin/androidx/ui/graphics/RectHelper.kt
index 06d24c1..7517871 100644
--- a/ui/ui-graphics/src/androidMain/kotlin/androidx/ui/graphics/RectHelper.kt
+++ b/ui/ui-graphics/src/androidMain/kotlin/androidx/ui/graphics/RectHelper.kt
@@ -42,3 +42,15 @@
         bottom
     )
 }
+
+/**
+ * Creates a new instance of [androidx.ui.geometry.Rect] with the same bounds
+ * specified in the given [android.graphics.Rect]
+ */
+fun android.graphics.Rect.toComposeRect(): androidx.ui.geometry.Rect =
+    androidx.ui.geometry.Rect(
+        this.left.toFloat(),
+        this.top.toFloat(),
+        this.right.toFloat(),
+        this.bottom.toFloat()
+    )
\ No newline at end of file
diff --git a/ui/ui-material/api/0.1.0-dev14.txt b/ui/ui-material/api/0.1.0-dev14.txt
index 5bd05e8..4d03fe99 100644
--- a/ui/ui-material/api/0.1.0-dev14.txt
+++ b/ui/ui-material/api/0.1.0-dev14.txt
@@ -278,6 +278,8 @@
     method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+    method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+    method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
   }
 
   @androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/api_lint.ignore b/ui/ui-material/api/api_lint.ignore
new file mode 100644
index 0000000..7e1573a
--- /dev/null
+++ b/ui/ui-material/api/api_lint.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+NotCloseable: androidx.ui.material.ripple.RippleEffect:
+    Classes that release resources (finish()) should implement AutoClosable and CloseGuard: class androidx.ui.material.ripple.RippleEffect
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index 5bd05e8..4d03fe99 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -278,6 +278,8 @@
     method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+    method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+    method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
   }
 
   @androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/public_plus_experimental_0.1.0-dev14.txt b/ui/ui-material/api/public_plus_experimental_0.1.0-dev14.txt
index 5bd05e8..4d03fe99 100644
--- a/ui/ui-material/api/public_plus_experimental_0.1.0-dev14.txt
+++ b/ui/ui-material/api/public_plus_experimental_0.1.0-dev14.txt
@@ -278,6 +278,8 @@
     method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+    method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+    method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
   }
 
   @androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/public_plus_experimental_current.txt b/ui/ui-material/api/public_plus_experimental_current.txt
index 5bd05e8..4d03fe99 100644
--- a/ui/ui-material/api/public_plus_experimental_current.txt
+++ b/ui/ui-material/api/public_plus_experimental_current.txt
@@ -278,6 +278,8 @@
     method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+    method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+    method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
   }
 
   @androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/restricted_0.1.0-dev14.txt b/ui/ui-material/api/restricted_0.1.0-dev14.txt
index 7bb3d57..200bcbe 100644
--- a/ui/ui-material/api/restricted_0.1.0-dev14.txt
+++ b/ui/ui-material/api/restricted_0.1.0-dev14.txt
@@ -279,6 +279,8 @@
     method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+    method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+    method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
   }
 
   @androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index 7bb3d57..200bcbe 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -279,6 +279,8 @@
     method @androidx.compose.Composable public static void FilledTextField-Vmoa5zc(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @androidx.compose.Composable public static void FilledTextField-o4GafH0(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
     method @Deprecated @androidx.compose.Composable public static void FilledTextField-u5y5QhY(androidx.ui.foundation.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.foundation.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+    method @androidx.compose.Composable public static void OutlinedTextField-CVdlBpA(androidx.ui.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.ui.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+    method @androidx.compose.Composable public static void OutlinedTextField-wI6llck(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.ui.core.Modifier modifier = Modifier, androidx.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.ui.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.ui.input.ImeAction,? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ ->  }, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFocusChange = {}, kotlin.jvm.functions.Function1<? super androidx.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
   }
 
   @androidx.compose.Immutable public final class Typography {
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemos.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemos.kt
index 2aaee29..f7c1dbb 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemos.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemos.kt
@@ -38,7 +38,7 @@
     ComposableDemo("Emphasis") { EmphasisSample() },
     ComposableDemo("ListItems") { ListItemDemo() },
     DemoCategory("TextFields", listOf(
-        ComposableDemo("FilledTextField") { FilledTextFieldDemo() },
+        ComposableDemo("FilledTextField/OutlinedTextField") { MaterialTextFieldDemo() },
         ComposableDemo("Multiple text fields") { TextFieldsDemo() }
     )),
     ComposableDemo("Material Theme") { MaterialThemeDemo() },
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt
index dbbca87..1c4d61d 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialTextField.kt
@@ -20,14 +20,12 @@
 import androidx.compose.getValue
 import androidx.compose.setValue
 import androidx.ui.core.Alignment
-import androidx.ui.core.DensityAmbient
-import androidx.ui.core.LayoutDirection
 import androidx.ui.core.Modifier
 import androidx.ui.foundation.Box
 import androidx.ui.foundation.Icon
 import androidx.ui.foundation.Text
+import androidx.ui.foundation.VerticalScroller
 import androidx.ui.graphics.Color
-import androidx.ui.layout.Arrangement
 import androidx.ui.layout.Column
 import androidx.ui.layout.ColumnScope.gravity
 import androidx.ui.layout.Row
@@ -40,6 +38,7 @@
 import androidx.ui.material.EmphasisAmbient
 import androidx.ui.material.FilledTextField
 import androidx.ui.material.MaterialTheme
+import androidx.ui.material.OutlinedTextField
 import androidx.ui.material.RadioGroup
 import androidx.ui.material.icons.Icons
 import androidx.ui.material.icons.filled.Favorite
@@ -49,55 +48,75 @@
 import androidx.ui.material.samples.FilledTextFieldWithIcons
 import androidx.ui.material.samples.FilledTextFieldWithPlaceholder
 import androidx.ui.material.samples.PasswordFilledTextField
+import androidx.ui.material.samples.SimpleOutlinedTextFieldSample
 import androidx.ui.material.samples.TextFieldWithHelperMessage
 import androidx.ui.material.samples.TextFieldWithHideKeyboardOnImeAction
 import androidx.ui.savedinstancestate.savedInstanceState
 import androidx.ui.unit.dp
+
 @Composable
 fun TextFieldsDemo() {
-    val space = with(DensityAmbient.current) { 5.dp.toIntPx() }
-    Column(
-        modifier = Modifier.fillMaxHeight().padding(10.dp),
-        verticalArrangement = arrangeWithSpacer(space)
-    ) {
-        Text("Password text field")
-        PasswordFilledTextField()
-        Text("Text field with leading and trailing icons")
-        FilledTextFieldWithIcons()
-        Text("Text field with placeholder")
-        FilledTextFieldWithPlaceholder()
-        Text("Text field with error state handling")
-        FilledTextFieldWithErrorState()
-        Text("Text field with helper/error message")
-        TextFieldWithHelperMessage()
-        Text("Hide keyboard on IME action")
-        TextFieldWithHideKeyboardOnImeAction()
-        Text("TextFieldValue overload")
-        FilledTextFieldSample()
+    VerticalScroller {
+        Column(
+            modifier = Modifier.fillMaxHeight().padding(10.dp)
+        ) {
+            Text("Password text field")
+            PasswordFilledTextField()
+            Text("Text field with leading and trailing icons")
+            FilledTextFieldWithIcons()
+            Text("Outlined text field")
+            SimpleOutlinedTextFieldSample()
+            Text("Text field with placeholder")
+            FilledTextFieldWithPlaceholder()
+            Text("Text field with error state handling")
+            FilledTextFieldWithErrorState()
+            Text("Text field with helper/error message")
+            TextFieldWithHelperMessage()
+            Text("Hide keyboard on IME action")
+            TextFieldWithHideKeyboardOnImeAction()
+            Text("TextFieldValue overload")
+            FilledTextFieldSample()
+        }
     }
 }
 
 @Composable
-fun FilledTextFieldDemo() {
+fun MaterialTextFieldDemo() {
     Column(Modifier.padding(10.dp)) {
         var text by savedInstanceState { "" }
         var leadingChecked by savedInstanceState { false }
         var trailingChecked by savedInstanceState { false }
         val characterCounterChecked by savedInstanceState { false }
         var selectedOption by savedInstanceState { Option.None }
+        var selectedTextField by savedInstanceState { TextFieldType.Filled }
 
-        val textField = @Composable {
-            FilledTextField(
-                value = text,
-                onValueChange = { text = it },
-                label = {
-                    val label = "Label" + if (selectedOption == Option.Error) "*" else ""
-                    Text(text = label)
-                },
-                leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
-                trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
-                isErrorValue = selectedOption == Option.Error
-            )
+        val textField: @Composable () -> Unit = @Composable {
+            when (selectedTextField) {
+                TextFieldType.Filled ->
+                    FilledTextField(
+                        value = text,
+                        onValueChange = { text = it },
+                        label = {
+                            val label = "Label" + if (selectedOption == Option.Error) "*" else ""
+                            Text(text = label)
+                        },
+                        leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
+                        trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
+                        isErrorValue = selectedOption == Option.Error
+                    )
+                TextFieldType.Outlined ->
+                    OutlinedTextField(
+                        value = text,
+                        onValueChange = { text = it },
+                        label = {
+                            val label = "Label" + if (selectedOption == Option.Error) "*" else ""
+                            Text(text = label)
+                        },
+                        leadingIcon = { if (leadingChecked) Icon(Icons.Filled.Favorite) },
+                        trailingIcon = { if (trailingChecked) Icon(Icons.Filled.Info) },
+                        isErrorValue = selectedOption == Option.Error
+                    )
+            }
         }
 
         Box(Modifier.preferredHeight(150.dp).gravity(Alignment.CenterHorizontally)) {
@@ -109,6 +128,13 @@
         }
 
         Column {
+            Title("Text field type")
+            RadioGroup(
+                options = TextFieldType.values().map { it.name },
+                selectedOption = selectedTextField.name,
+                onSelectedChange = { selectedTextField = TextFieldType.valueOf(it) }
+            )
+
             Title("Options")
             OptionRow(
                 title = "Leading icon",
@@ -193,18 +219,4 @@
  */
 private enum class Option { None, Helper, Error }
 
-private fun arrangeWithSpacer(space: Int) = object : Arrangement.Vertical {
-    override fun arrange(
-        totalSize: Int,
-        size: List<Int>,
-        layoutDirection: LayoutDirection
-    ): List<Int> {
-        val positions = mutableListOf<Int>()
-        var current = 0
-        size.forEach {
-            positions.add(current)
-            current += (it + space)
-        }
-        return positions
-    }
-}
\ No newline at end of file
+private enum class TextFieldType { Filled, Outlined }
diff --git a/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/TextFieldSamples.kt b/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/TextFieldSamples.kt
index a472fef..c3f4da4 100644
--- a/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/TextFieldSamples.kt
+++ b/ui/ui-material/samples/src/main/java/androidx/ui/material/samples/TextFieldSamples.kt
@@ -32,6 +32,7 @@
 import androidx.ui.material.EmphasisAmbient
 import androidx.ui.material.FilledTextField
 import androidx.ui.material.MaterialTheme
+import androidx.ui.material.OutlinedTextField
 import androidx.ui.material.icons.Icons
 import androidx.ui.material.icons.filled.Favorite
 import androidx.ui.material.icons.filled.Info
@@ -53,6 +54,18 @@
 
 @Sampled
 @Composable
+fun SimpleOutlinedTextFieldSample() {
+    var text by savedInstanceState { "" }
+
+    OutlinedTextField(
+        value = text,
+        onValueChange = { text = it },
+        label = { Text("Label") }
+    )
+}
+
+@Sampled
+@Composable
 fun FilledTextFieldWithIcons() {
     var text by savedInstanceState { "" }
 
@@ -153,6 +166,20 @@
 
 @Sampled
 @Composable
+fun OutlinedTextFieldSample() {
+    var text by savedInstanceState(saver = TextFieldValue.Saver) {
+        TextFieldValue("example", TextRange(0, 7))
+    }
+
+    OutlinedTextField(
+        value = text,
+        onValueChange = { text = it },
+        label = { Text("Label") }
+    )
+}
+
+@Sampled
+@Composable
 fun TextFieldWithHideKeyboardOnImeAction() {
     var text by savedInstanceState { "" }
 
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/FilledTextFieldTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/FilledTextFieldTest.kt
deleted file mode 100644
index b9256b4..0000000
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/FilledTextFieldTest.kt
+++ /dev/null
@@ -1,961 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.ui.material
-
-import android.os.Build
-import androidx.compose.Providers
-import androidx.compose.remember
-import androidx.compose.state
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import androidx.ui.core.Modifier
-import androidx.ui.core.Ref
-import androidx.ui.core.TextInputServiceAmbient
-import androidx.ui.core.globalPosition
-import androidx.ui.core.onPositioned
-import androidx.ui.core.positionInRoot
-import androidx.ui.core.testTag
-import androidx.ui.foundation.Box
-import androidx.ui.foundation.Text
-import androidx.ui.foundation.TextField
-import androidx.ui.foundation.contentColor
-import androidx.ui.foundation.currentTextStyle
-import androidx.ui.foundation.drawBackground
-import androidx.ui.geometry.Offset
-import androidx.ui.graphics.Color
-import androidx.ui.graphics.RectangleShape
-import androidx.ui.graphics.compositeOver
-import androidx.ui.input.ImeAction
-import androidx.ui.input.KeyboardType
-import androidx.ui.input.PasswordVisualTransformation
-import androidx.ui.input.TextFieldValue
-import androidx.ui.input.TextInputService
-import androidx.ui.layout.Column
-import androidx.ui.layout.Stack
-import androidx.ui.layout.fillMaxWidth
-import androidx.ui.layout.preferredHeight
-import androidx.ui.layout.preferredSize
-import androidx.ui.savedinstancestate.rememberSavedInstanceState
-import androidx.ui.test.StateRestorationTester
-import androidx.ui.test.assertPixels
-import androidx.ui.test.assertShape
-import androidx.ui.test.captureToBitmap
-import androidx.ui.test.createComposeRule
-import androidx.ui.test.doClick
-import androidx.ui.test.doGesture
-import androidx.ui.test.doSendImeAction
-import androidx.ui.test.findByTag
-import androidx.ui.test.runOnIdleCompose
-import androidx.ui.test.sendClick
-import androidx.ui.test.sendSwipeDown
-import androidx.ui.test.sendSwipeUp
-import androidx.ui.test.waitForIdle
-import androidx.ui.text.FirstBaseline
-import androidx.ui.text.SoftwareKeyboardController
-import androidx.ui.unit.IntSize
-import androidx.ui.unit.dp
-import androidx.ui.unit.sp
-import com.google.common.truth.Truth.assertThat
-import com.nhaarman.mockitokotlin2.any
-import com.nhaarman.mockitokotlin2.atLeastOnce
-import com.nhaarman.mockitokotlin2.eq
-import com.nhaarman.mockitokotlin2.mock
-import com.nhaarman.mockitokotlin2.verify
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-import kotlin.math.roundToInt
-
-@MediumTest
-@RunWith(JUnit4::class)
-class FilledTextFieldTest {
-
-    private val ExpectedMinimumTextFieldHeight = 56.dp
-    private val ExpectedPadding = 16.dp
-    private val IconPadding = 12.dp
-    private val ExpectedBaselineOffset = 20.dp
-    private val ExpectedLastBaselineOffset = 16.dp
-    private val IconColorAlpha = 0.54f
-
-    private val LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
-            "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
-            " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
-            "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
-            "fugiat nulla pariatur."
-
-    @get:Rule
-    val testRule = createComposeRule()
-
-    @Test
-    fun testTextFieldMinimumHeight() {
-        testRule
-            .setMaterialContentAndCollectSizes {
-                FilledTextField(
-                    value = "input",
-                    onValueChange = {},
-                    label = {},
-                    modifier = Modifier.preferredHeight(20.dp)
-                )
-            }
-            .assertHeightEqualsTo(ExpectedMinimumTextFieldHeight)
-    }
-
-    @Test
-    fun testTextField_singleFocus() {
-        var textField1Focused = false
-        val textField1Tag = "TextField1"
-
-        var textField2Focused = false
-        val textField2Tag = "TextField2"
-
-        testRule.setMaterialContent {
-            Column {
-                FilledTextField(
-                    modifier = Modifier.testTag(textField1Tag),
-                    value = "input1",
-                    onValueChange = {},
-                    label = {},
-                    onFocusChange = { textField1Focused = it }
-                )
-                FilledTextField(
-                    modifier = Modifier.testTag(textField2Tag),
-                    value = "input2",
-                    onValueChange = {},
-                    label = {},
-                    onFocusChange = { textField2Focused = it }
-                )
-            }
-        }
-
-        findByTag(textField1Tag).doClick()
-
-        runOnIdleCompose {
-            assertThat(textField1Focused).isTrue()
-            assertThat(textField2Focused).isFalse()
-        }
-
-        findByTag(textField2Tag).doClick()
-
-        runOnIdleCompose {
-            assertThat(textField1Focused).isFalse()
-            assertThat(textField2Focused).isTrue()
-        }
-    }
-
-    @Test
-    fun testGetFocus_whenClickedOnSurfaceArea() {
-        var focused = false
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    modifier = Modifier.testTag("textField"),
-                    value = "input",
-                    onValueChange = {},
-                    label = {},
-                    onFocusChange = { focused = it }
-                )
-            }
-        }
-
-        // Click on (2, 2) which is Surface area and outside input area
-        findByTag("textField").doGesture {
-            sendClick(Offset(2f, 2f))
-        }
-
-        testRule.runOnIdleComposeWithDensity {
-            assertThat(focused).isTrue()
-        }
-    }
-
-    @Test
-    fun testLabelPosition_initial_withDefaultHeight() {
-        val labelSize = Ref<IntSize>()
-        val labelPosition = Ref<Offset>()
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            fontSize = 10.sp,
-                            modifier = Modifier
-                                .onPositioned {
-                                    labelPosition.value = it.globalPosition
-                                    labelSize.value = it.size
-                                }
-                        )
-                    },
-                    modifier = Modifier.preferredHeight(56.dp)
-                )
-            }
-        }
-
-        testRule.runOnIdleComposeWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // centered position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.toIntPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ((ExpectedMinimumTextFieldHeight.toIntPx() - labelSize.value!!.height) / 2f)
-                    .roundToInt().toFloat()
-            )
-        }
-    }
-
-    @Test
-    fun testLabelPosition_initial_withCustomHeight() {
-        val height = 80.dp
-        val labelSize = Ref<IntSize>()
-        val labelPosition = Ref<Offset>()
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.preferredHeight(height),
-                    label = {
-                        Text(text = "label", modifier = Modifier.onPositioned {
-                            labelPosition.value = it.globalPosition
-                            labelSize.value = it.size
-                        })
-                    }
-                )
-            }
-        }
-
-        testRule.runOnIdleComposeWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // centered position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.toIntPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ((height.toIntPx() - labelSize.value!!.height) / 2f).roundToInt().toFloat()
-            )
-        }
-    }
-
-    @Test
-    fun testLabelPosition_whenFocused() {
-        val labelSize = Ref<IntSize>()
-        val labelPosition = Ref<Offset>()
-        val baseline = Ref<Float>()
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    modifier = Modifier.testTag("textField"),
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(text = "label", modifier = Modifier.onPositioned {
-                            labelPosition.value = it.globalPosition
-                            labelSize.value = it.size
-                            baseline.value =
-                                it[FirstBaseline]!!.toFloat() + labelPosition.value!!.y
-                        })
-                    }
-                )
-            }
-        }
-
-        // click to focus
-        clickAndAdvanceClock("textField", 200)
-
-        testRule.runOnIdleComposeWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // label's top position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.toIntPx().toFloat()
-            )
-            assertThat(baseline.value).isEqualTo(
-                ExpectedBaselineOffset.toIntPx().toFloat()
-            )
-        }
-    }
-
-    @Test
-    fun testLabelPosition_whenInput() {
-        val labelSize = Ref<IntSize>()
-        val labelPosition = Ref<Offset>()
-        val baseline = Ref<Float>()
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    value = "input",
-                    onValueChange = {},
-                    label = {
-                        Text(text = "label", modifier = Modifier.onPositioned {
-                            labelPosition.value = it.globalPosition
-                            labelSize.value = it.size
-                            baseline.value =
-                                it[FirstBaseline]!!.toFloat() + labelPosition.value!!.y
-                        })
-                    }
-                )
-            }
-        }
-
-        testRule.runOnIdleComposeWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // label's top position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.toIntPx().toFloat()
-            )
-            assertThat(baseline.value).isEqualTo(
-                ExpectedBaselineOffset.toIntPx().toFloat()
-            )
-        }
-    }
-
-    @Test
-    fun testPlaceholderPosition_withLabel() {
-        val placeholderSize = Ref<IntSize>()
-        val placeholderPosition = Ref<Offset>()
-        val placeholderBaseline = Ref<Float>()
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    modifier = Modifier
-                        .preferredHeight(60.dp)
-                        .testTag("textField"),
-                    value = "",
-                    onValueChange = {},
-                    label = { Text("label") },
-                    placeholder = {
-                        Text(text = "placeholder", modifier = Modifier.onPositioned {
-                            placeholderPosition.value = it.globalPosition
-                            placeholderSize.value = it.size
-                            placeholderBaseline.value =
-                                it[FirstBaseline]!!.toFloat() + placeholderPosition.value!!.y
-                        })
-                    }
-                )
-            }
-        }
-        // click to focus
-        clickAndAdvanceClock("textField", 200)
-
-        testRule.runOnIdleComposeWithDensity {
-            // size
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isGreaterThan(0)
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // placeholder's position
-            assertThat(placeholderPosition.value?.x).isEqualTo(
-                ExpectedPadding.toIntPx().toFloat()
-            )
-            assertThat(placeholderBaseline.value)
-                .isEqualTo(
-                    60.dp.toIntPx().toFloat() -
-                            ExpectedLastBaselineOffset.toIntPx().toFloat()
-                )
-        }
-    }
-
-    @Test
-    fun testPlaceholderPosition_whenNoLabel() {
-        val placeholderSize = Ref<IntSize>()
-        val placeholderPosition = Ref<Offset>()
-        val placeholderBaseline = Ref<Float>()
-        val height = 60.dp
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    modifier = Modifier.preferredHeight(height).testTag("textField"),
-                    value = "",
-                    onValueChange = {},
-                    label = {},
-                    placeholder = {
-                        Text(text = "placeholder", modifier = Modifier.onPositioned {
-                            placeholderPosition.value = it.positionInRoot
-                            placeholderSize.value = it.size
-                            placeholderBaseline.value =
-                                it[FirstBaseline]!!.toFloat() + placeholderPosition.value!!.y
-                        })
-                    }
-                )
-            }
-        }
-        // click to focus
-        clickAndAdvanceClock("textField", 200)
-
-        testRule.runOnIdleComposeWithDensity {
-            // size
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isGreaterThan(0)
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // centered position
-            assertThat(placeholderPosition.value?.x).isEqualTo(
-                ExpectedPadding.toIntPx().toFloat()
-            )
-            assertThat(placeholderPosition.value?.y).isEqualTo(
-                ((height.toIntPx() - placeholderSize.value!!.height) / 2f).roundToInt().toFloat()
-            )
-        }
-    }
-
-    @Test
-    fun testNoPlaceholder_whenInputNotEmpty() {
-        val placeholderSize = Ref<IntSize>()
-        val placeholderPosition = Ref<Offset>()
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    modifier = Modifier.testTag("textField"),
-                    value = "input",
-                    onValueChange = {},
-                    label = {},
-                    placeholder = {
-                        Text(text = "placeholder", modifier = Modifier.onPositioned {
-                            placeholderPosition.value = it.globalPosition
-                            placeholderSize.value = it.size
-                        })
-                    }
-                )
-            }
-        }
-
-        // click to focus
-        clickAndAdvanceClock("textField", 200)
-
-        testRule.runOnIdleComposeWithDensity {
-            assertThat(placeholderSize.value).isNull()
-            assertThat(placeholderPosition.value).isNull()
-        }
-    }
-
-    @Test
-    fun testPlaceholderColorAndTextStyle() {
-        testRule.setMaterialContent {
-            FilledTextField(
-                modifier = Modifier.testTag("textField"),
-                value = "",
-                onValueChange = {},
-                label = {},
-                placeholder = {
-                    Text("placeholder")
-                    assertThat(contentColor())
-                        .isEqualTo(MaterialTheme.colors.onSurface.copy(0.6f))
-                    assertThat(currentTextStyle()).isEqualTo(MaterialTheme.typography.subtitle1)
-                }
-            )
-        }
-
-        // click to focus
-        findByTag("textField").doClick()
-    }
-
-    @Test
-    fun testTrailingAndLeading_sizeAndPosition() {
-        val textFieldHeight = 60.dp
-        val textFieldWidth = 300.dp
-        val size = 30.dp
-        val leadingPosition = Ref<Offset>()
-        val leadingSize = Ref<IntSize>()
-        val trailingPosition = Ref<Offset>()
-        val trailingSize = Ref<IntSize>()
-
-        testRule.setMaterialContent {
-            FilledTextField(
-                value = "text",
-                onValueChange = {},
-                modifier = Modifier.preferredSize(textFieldWidth, textFieldHeight),
-                label = {},
-                leadingIcon = {
-                    Box(Modifier.preferredSize(size).onPositioned {
-                        leadingPosition.value = it.globalPosition
-                        leadingSize.value = it.size
-                    })
-                },
-                trailingIcon = {
-                    Box(Modifier.preferredSize(size).onPositioned {
-                        trailingPosition.value = it.globalPosition
-                        trailingSize.value = it.size
-                    })
-                }
-            )
-        }
-
-        testRule.runOnIdleComposeWithDensity {
-            // leading
-            assertThat(leadingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
-            assertThat(leadingPosition.value?.x).isEqualTo(IconPadding.toIntPx().toFloat())
-            assertThat(leadingPosition.value?.y).isEqualTo(
-                ((textFieldHeight.toIntPx() - leadingSize.value!!.height) / 2f).roundToInt()
-                    .toFloat()
-            )
-            // trailing
-            assertThat(trailingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
-            assertThat(trailingPosition.value?.x).isEqualTo(
-                (textFieldWidth.toIntPx() - IconPadding.toIntPx() - trailingSize.value!!.width)
-                    .toFloat()
-            )
-            assertThat(trailingPosition.value?.y)
-                .isEqualTo(((textFieldHeight.toIntPx() - trailingSize.value!!.height) / 2f)
-                    .roundToInt().toFloat()
-                )
-        }
-    }
-
-    @Test
-    fun testLabelPositionX_initial_withTrailingAndLeading() {
-        val height = 60.dp
-        val iconSize = 30.dp
-        val labelPosition = Ref<Offset>()
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.preferredHeight(height),
-                    label = {
-                        Text(text = "label", modifier = Modifier.onPositioned {
-                            labelPosition.value = it.globalPosition
-                        })
-                    },
-                    trailingIcon = { Box(Modifier.preferredSize(iconSize)) },
-                    leadingIcon = { Box(Modifier.preferredSize(iconSize)) }
-                )
-            }
-        }
-
-        testRule.runOnIdleComposeWithDensity {
-            assertThat(labelPosition.value?.x).isEqualTo(
-                (ExpectedPadding.toIntPx() + IconPadding.toIntPx() + iconSize.toIntPx())
-                    .toFloat()
-            )
-        }
-    }
-
-    @Test
-    fun testLabelPositionX_initial_withEmptyTrailingAndLeading() {
-        val height = 60.dp
-        val labelPosition = Ref<Offset>()
-        testRule.setMaterialContent {
-            Box {
-                FilledTextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.preferredHeight(height),
-                    label = {
-                        Text(text = "label", modifier = Modifier.onPositioned {
-                            labelPosition.value = it.globalPosition
-                        })
-                    },
-                    trailingIcon = {},
-                    leadingIcon = {}
-                )
-            }
-        }
-
-        testRule.runOnIdleComposeWithDensity {
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.toIntPx().toFloat()
-            )
-        }
-    }
-
-    @Test
-    fun testColorInLeadingTrailing_whenValidInput() {
-        testRule.setMaterialContent {
-            FilledTextField(
-                value = "",
-                onValueChange = {},
-                label = {},
-                isErrorValue = false,
-                leadingIcon = {
-                    assertThat(contentColor())
-                        .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
-                },
-                trailingIcon = {
-                    assertThat(contentColor())
-                        .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
-                }
-            )
-        }
-    }
-
-    @Test
-    fun testColorInLeadingTrailing_whenInvalidInput() {
-        testRule.setMaterialContent {
-            FilledTextField(
-                value = "",
-                onValueChange = {},
-                label = {},
-                isErrorValue = true,
-                leadingIcon = {
-                    assertThat(contentColor())
-                        .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
-                },
-                trailingIcon = {
-                    assertThat(contentColor()).isEqualTo(MaterialTheme.colors.error)
-                }
-            )
-        }
-    }
-
-    @Test
-    fun testImeActionAndKeyboardTypePropagatedDownstream() {
-        val textInputService = mock<TextInputService>()
-        testRule.setContent {
-            Providers(
-                TextInputServiceAmbient provides textInputService
-            ) {
-                var text = state { TextFieldValue("") }
-                FilledTextField(
-                    modifier = Modifier.testTag("textField"),
-                    value = text.value,
-                    onValueChange = { text.value = it },
-                    label = {},
-                    imeAction = ImeAction.Go,
-                    keyboardType = KeyboardType.Email
-                )
-            }
-        }
-
-        clickAndAdvanceClock("textField", 200)
-
-        runOnIdleCompose {
-            verify(textInputService, atLeastOnce()).startInput(
-                value = any(),
-                keyboardType = eq(KeyboardType.Email),
-                imeAction = eq(ImeAction.Go),
-                onEditCommand = any(),
-                onImeActionPerformed = any()
-            )
-        }
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun testVisualTransformationPropagated() {
-        testRule.setMaterialContent {
-            FilledTextField(
-                modifier = Modifier.testTag("textField"),
-                value = "qwerty",
-                onValueChange = {},
-                label = {},
-                visualTransformation = PasswordVisualTransformation('\u0020'),
-                backgroundColor = Color.White,
-                shape = RectangleShape
-            )
-        }
-
-        findByTag("textField")
-            .captureToBitmap()
-            .assertShape(
-                density = testRule.density,
-                backgroundColor = Color.White,
-                shapeColor = Color.White,
-                shape = RectangleShape,
-                // avoid elevation artifacts
-                shapeOverlapPixelCount = with(testRule.density) { 3.dp.toPx() }
-            )
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun test_alphaNotSet_toBackgroundColorAndTransparentColors() {
-        val latch = CountDownLatch(1)
-
-        testRule.setMaterialContent {
-            Stack(Modifier.drawBackground(Color.White)) {
-                FilledTextField(
-                    modifier = Modifier.testTag("textField"),
-                    value = "",
-                    onValueChange = {},
-                    label = {},
-                    shape = RectangleShape,
-                    backgroundColor = Color.Blue,
-                    activeColor = Color.Transparent,
-                    inactiveColor = Color.Transparent,
-                    onFocusChange = { focused ->
-                        if (focused) latch.countDown()
-                    }
-                )
-            }
-        }
-
-        val expectedColor = Color.Blue.copy(alpha = 0.12f).compositeOver(Color.White)
-
-        findByTag("textField")
-            .captureToBitmap()
-            .assertShape(
-                density = testRule.density,
-                backgroundColor = Color.White,
-                shapeColor = expectedColor,
-                shape = RectangleShape,
-                // avoid elevation artifacts
-                shapeOverlapPixelCount = with(testRule.density) { 1.dp.toPx() }
-            )
-
-        findByTag("textField").doClick()
-        assert(latch.await(1, TimeUnit.SECONDS))
-
-        findByTag("textField")
-            .captureToBitmap()
-            .assertShape(
-                density = testRule.density,
-                backgroundColor = Color.White,
-                shapeColor = expectedColor,
-                shape = RectangleShape,
-                // avoid elevation artifacts
-                shapeOverlapPixelCount = with(testRule.density) { 1.dp.toPx() }
-            )
-    }
-
-    @Test
-    fun testOnTextInputStartedCallback() {
-        var controller: SoftwareKeyboardController? = null
-
-        testRule.setMaterialContent {
-            FilledTextField(
-                modifier = Modifier.testTag("textField"),
-                value = "",
-                onValueChange = {},
-                label = {},
-                onTextInputStarted = {
-                    controller = it
-                }
-            )
-        }
-        assertThat(controller).isNull()
-
-        findByTag("textField")
-            .doClick()
-
-        runOnIdleCompose {
-            assertThat(controller).isNotNull()
-        }
-    }
-
-    @Test
-    fun testImeActionCallback_withSoftwareKeyboardController() {
-        var controller: SoftwareKeyboardController? = null
-
-        testRule.setMaterialContent {
-            FilledTextField(
-                modifier = Modifier.testTag("textField"),
-                value = "",
-                onValueChange = {},
-                label = {},
-                imeAction = ImeAction.Go,
-                onImeActionPerformed = { _, softwareKeyboardController ->
-                    controller = softwareKeyboardController
-                }
-            )
-        }
-        assertThat(controller).isNull()
-
-        findByTag("textField")
-            .doSendImeAction()
-
-        runOnIdleCompose {
-            assertThat(controller).isNotNull()
-        }
-    }
-
-    @Test
-    fun textField_scrollable_withLongInput() {
-        val scrollerPosition = TextFieldScrollerPosition()
-        testRule.setContent {
-            Stack {
-                TextFieldScroller(
-                    remember { scrollerPosition },
-                    Modifier.fillMaxWidth().preferredHeight(40.dp)
-                ) {
-                    TextField(
-                        value = TextFieldValue(LONG_TEXT),
-                        onValueChange = {}
-                    )
-                }
-            }
-        }
-
-        runOnIdleCompose {
-            assertThat(scrollerPosition.maximum).isLessThan(Float.POSITIVE_INFINITY)
-            assertThat(scrollerPosition.maximum).isGreaterThan(0f)
-        }
-    }
-
-    @Test
-    fun textField_notScrollable_withShortInput() {
-        val text = "text"
-        val scrollerPosition = TextFieldScrollerPosition()
-        testRule.setContent {
-            Stack {
-                TextFieldScroller(
-                    remember { scrollerPosition },
-                    Modifier.fillMaxWidth().preferredHeight(100.dp)
-                ) {
-                    TextField(
-                        value = TextFieldValue(text),
-                        onValueChange = {}
-                    )
-                }
-            }
-        }
-
-        runOnIdleCompose {
-            assertThat(scrollerPosition.maximum).isEqualTo(0f)
-        }
-    }
-
-    @Test
-    fun textField_scrolledAndClipped() {
-        val tag = "testTag"
-        val scrollerPosition = TextFieldScrollerPosition()
-
-        val parentSize = 200
-        val textFieldSize = 50
-
-        with(testRule.density) {
-            testRule.setContent {
-                Stack(
-                    Modifier
-                        .preferredSize(parentSize.toDp())
-                        .drawBackground(Color.White)
-                        .testTag(tag)
-                ) {
-                    TextFieldScroller(
-                        remember { scrollerPosition },
-                        Modifier.preferredSize(textFieldSize.toDp())
-                    ) {
-                        TextField(
-                            value = TextFieldValue(LONG_TEXT),
-                            onValueChange = {}
-                        )
-                    }
-                }
-            }
-        }
-
-        runOnIdleCompose {}
-
-        findByTag(tag)
-            .captureToBitmap()
-            .assertPixels(expectedSize = IntSize(parentSize, parentSize)) { position ->
-                if (position.x > textFieldSize && position.y > textFieldSize) Color.White else null
-            }
-    }
-
-    @Test
-    fun textField_swipe_whenLongInput() {
-        val tag = "testTag"
-        val scrollerPosition = TextFieldScrollerPosition()
-
-        testRule.setContent {
-            Stack {
-                TextFieldScroller(
-                    remember { scrollerPosition },
-                    Modifier.fillMaxWidth().preferredHeight(40.dp).testTag(tag)
-                ) {
-                    TextField(
-                        value = TextFieldValue(LONG_TEXT),
-                        onValueChange = {}
-                    )
-                }
-            }
-        }
-
-        runOnIdleCompose {
-            assertThat(scrollerPosition.current).isEqualTo(0f)
-        }
-
-        findByTag(tag)
-            .doGesture { sendSwipeDown() }
-
-        val firstSwipePosition = runOnIdleCompose {
-            scrollerPosition.current
-        }
-        assertThat(firstSwipePosition).isGreaterThan(0f)
-
-        findByTag(tag)
-            .doGesture { sendSwipeUp() }
-        runOnIdleCompose {
-            assertThat(scrollerPosition.current).isLessThan(firstSwipePosition)
-        }
-    }
-
-    @Test
-    fun textFieldScroller_restoresScrollerPosition() {
-        val tag = "scroller"
-        val restorationTester = StateRestorationTester(testRule)
-        var scrollerPosition = TextFieldScrollerPosition()
-
-        restorationTester.setContent {
-            scrollerPosition = rememberSavedInstanceState(
-                saver = TextFieldScrollerPosition.Saver
-            ) {
-                TextFieldScrollerPosition()
-            }
-            TextFieldScroller(
-                scrollerPosition,
-                Modifier.fillMaxWidth().preferredHeight(40.dp).testTag(tag)
-            ) {
-                TextField(
-                    value = TextFieldValue(LONG_TEXT),
-                    onValueChange = {}
-                )
-            }
-        }
-
-        findByTag(tag)
-            .doGesture { sendSwipeDown() }
-
-        val swipePosition = runOnIdleCompose {
-            scrollerPosition.current
-        }
-        assertThat(swipePosition).isGreaterThan(0f)
-
-        runOnIdleCompose {
-            scrollerPosition = TextFieldScrollerPosition()
-            assertThat(scrollerPosition.current).isEqualTo(0f)
-        }
-
-        restorationTester.emulateSavedInstanceStateRestore()
-
-        runOnIdleCompose {
-            assertThat(scrollerPosition.current).isEqualTo(swipePosition)
-        }
-    }
-
-    private fun clickAndAdvanceClock(tag: String, time: Long) {
-        findByTag(tag).doClick()
-        waitForIdle()
-        testRule.clockTestRule.pauseClock()
-        testRule.clockTestRule.advanceClock(time)
-    }
-}
\ No newline at end of file
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/TextFieldTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextFieldTest.kt
new file mode 100644
index 0000000..b56c46d
--- /dev/null
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextFieldTest.kt
@@ -0,0 +1,1488 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.material
+
+import android.os.Build
+import androidx.compose.Providers
+import androidx.compose.getValue
+import androidx.compose.remember
+import androidx.compose.setValue
+import androidx.compose.state
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.ui.core.Modifier
+import androidx.ui.core.Ref
+import androidx.ui.core.TextInputServiceAmbient
+import androidx.ui.core.globalPosition
+import androidx.ui.core.onPositioned
+import androidx.ui.core.positionInRoot
+import androidx.ui.core.testTag
+import androidx.ui.foundation.Box
+import androidx.ui.foundation.Text
+import androidx.ui.foundation.TextField
+import androidx.ui.foundation.contentColor
+import androidx.ui.foundation.currentTextStyle
+import androidx.ui.foundation.drawBackground
+import androidx.ui.geometry.Offset
+import androidx.ui.graphics.Color
+import androidx.ui.graphics.RectangleShape
+import androidx.ui.graphics.compositeOver
+import androidx.ui.input.ImeAction
+import androidx.ui.input.KeyboardType
+import androidx.ui.input.PasswordVisualTransformation
+import androidx.ui.input.TextFieldValue
+import androidx.ui.input.TextInputService
+import androidx.ui.layout.Column
+import androidx.ui.layout.Stack
+import androidx.ui.layout.fillMaxWidth
+import androidx.ui.layout.preferredHeight
+import androidx.ui.layout.preferredSize
+import androidx.ui.savedinstancestate.rememberSavedInstanceState
+import androidx.ui.test.StateRestorationTester
+import androidx.ui.test.assertPixels
+import androidx.ui.test.assertShape
+import androidx.ui.test.captureToBitmap
+import androidx.ui.test.createComposeRule
+import androidx.ui.test.doClick
+import androidx.ui.test.doGesture
+import androidx.ui.test.doSendImeAction
+import androidx.ui.test.findByTag
+import androidx.ui.test.runOnIdleCompose
+import androidx.ui.test.sendClick
+import androidx.ui.test.sendSwipeDown
+import androidx.ui.test.sendSwipeUp
+import androidx.ui.test.waitForIdle
+import androidx.ui.text.FirstBaseline
+import androidx.ui.text.SoftwareKeyboardController
+import androidx.ui.unit.IntSize
+import androidx.ui.unit.dp
+import androidx.ui.unit.sp
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.atLeastOnce
+import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.math.roundToInt
+import androidx.ui.layout.preferredWidth
+
+@MediumTest
+@RunWith(JUnit4::class)
+class TextFieldTest {
+
+    private val ExpectedMinimumTextFieldHeight = 56.dp
+    private val ExpectedPadding = 16.dp
+    private val IconPadding = 12.dp
+    private val ExpectedBaselineOffset = 20.dp
+    private val ExpectedLastBaselineOffset = 16.dp
+    private val IconColorAlpha = 0.54f
+    private val TextfieldTag = "textField"
+
+    private val LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+            "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
+            " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
+            "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
+            "fugiat nulla pariatur."
+
+    @get:Rule
+    val testRule = createComposeRule()
+
+    @Test
+    fun testFilledTextField_minimumHeight() {
+        testRule
+            .setMaterialContentAndCollectSizes {
+                FilledTextField(
+                    value = "input",
+                    onValueChange = {},
+                    label = {},
+                    modifier = Modifier.preferredHeight(20.dp)
+                )
+            }
+            .assertHeightEqualsTo(ExpectedMinimumTextFieldHeight)
+    }
+
+    @Test
+    fun testTextFields_singleFocus() {
+        var textField1Focused = false
+        val textField1Tag = "TextField1"
+
+        var textField2Focused = false
+        val textField2Tag = "TextField2"
+
+        var textField3Focused = false
+        val textField3Tag = "TextField3"
+
+        testRule.setMaterialContent {
+            Column {
+                FilledTextField(
+                    modifier = Modifier.testTag(textField1Tag),
+                    value = "input1",
+                    onValueChange = {},
+                    label = {},
+                    onFocusChange = { textField1Focused = it }
+                )
+                OutlinedTextField(
+                    modifier = Modifier.testTag(textField2Tag),
+                    value = "input2",
+                    onValueChange = {},
+                    label = {},
+                    onFocusChange = { textField2Focused = it }
+                )
+                FilledTextField(
+                    modifier = Modifier.testTag(textField3Tag),
+                    value = "input2",
+                    onValueChange = {},
+                    label = {},
+                    onFocusChange = { textField3Focused = it }
+                )
+            }
+        }
+
+        findByTag(textField1Tag).doClick()
+
+        runOnIdleCompose {
+            assertThat(textField1Focused).isTrue()
+            assertThat(textField2Focused).isFalse()
+            assertThat(textField3Focused).isFalse()
+        }
+
+        findByTag(textField2Tag).doClick()
+
+        runOnIdleCompose {
+            assertThat(textField1Focused).isFalse()
+            assertThat(textField2Focused).isTrue()
+            assertThat(textField3Focused).isFalse()
+        }
+
+        findByTag(textField3Tag).doClick()
+
+        runOnIdleCompose {
+            assertThat(textField1Focused).isFalse()
+            assertThat(textField2Focused).isFalse()
+            assertThat(textField3Focused).isTrue()
+        }
+    }
+
+    @Test
+    fun testFilledTextField_getFocus_whenClickedOnSurfaceArea() {
+        var focused = false
+        testRule.setMaterialContent {
+            Box {
+                FilledTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = "input",
+                    onValueChange = {},
+                    label = {},
+                    onFocusChange = { focused = it }
+                )
+            }
+        }
+
+        // Click on (2, 2) which is Surface area and outside input area
+        findByTag(TextfieldTag).doGesture {
+            sendClick(Offset(2f, 2f))
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            assertThat(focused).isTrue()
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_getFocus_whenClickedOnInternalArea() {
+        var focused = false
+        testRule.setMaterialContent {
+            Box {
+                OutlinedTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = "input",
+                    onValueChange = {},
+                    label = {},
+                    onFocusChange = { focused = it }
+                )
+            }
+        }
+
+        // Click on (2, 2) which is a background area and outside input area
+        findByTag(TextfieldTag).doGesture {
+            sendClick(Offset(2f, 2f))
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            assertThat(focused).isTrue()
+        }
+    }
+
+    @Test
+    fun testFilledTextField_labelPosition_initial_withDefaultHeight() {
+        val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Box {
+                FilledTextField(
+                    value = "",
+                    onValueChange = {},
+                    label = {
+                        Text(
+                            text = "label",
+                            fontSize = 10.sp,
+                            modifier = Modifier
+                                .onPositioned {
+                                    labelPosition.value = it.globalPosition
+                                    labelSize.value = it.size
+                                }
+                        )
+                    },
+                    modifier = Modifier.preferredHeight(56.dp)
+                )
+            }
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(labelSize.value).isNotNull()
+            assertThat(labelSize.value?.height).isGreaterThan(0)
+            assertThat(labelSize.value?.width).isGreaterThan(0)
+            // centered position
+            assertThat(labelPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            assertThat(labelPosition.value?.y).isEqualTo(
+                ((ExpectedMinimumTextFieldHeight.toIntPx() - labelSize.value!!.height) / 2f)
+                    .roundToInt().toFloat()
+            )
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_labelPosition_initial_withDefaultHeight() {
+        val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Box {
+                OutlinedTextField(
+                    value = "",
+                    onValueChange = {},
+                    label = {
+                        Text(text = "label", modifier = Modifier.onPositioned {
+                            labelPosition.value = it.globalPosition
+                            labelSize.value = it.size
+                        })
+                    }
+                )
+            }
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(labelSize.value).isNotNull()
+            assertThat(labelSize.value?.height).isGreaterThan(0)
+            assertThat(labelSize.value?.width).isGreaterThan(0)
+            assertThat(labelPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            // label is centered in 56.dp default container, plus additional 8.dp padding on top
+            val minimumHeight = ExpectedMinimumTextFieldHeight.toIntPx()
+            assertThat(labelPosition.value?.y).isEqualTo(
+                ((minimumHeight - labelSize.value!!.height) / 2f).roundToInt() + 8.dp.toIntPx()
+            )
+        }
+    }
+
+    @Test
+    fun testFilledTextField_labelPosition_initial_withCustomHeight() {
+        val height = 80.dp
+        val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Box {
+                FilledTextField(
+                    value = "",
+                    onValueChange = {},
+                    modifier = Modifier.preferredHeight(height),
+                    label = {
+                        Text(text = "label", modifier = Modifier.onPositioned {
+                            labelPosition.value = it.globalPosition
+                            labelSize.value = it.size
+                        })
+                    }
+                )
+            }
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(labelSize.value).isNotNull()
+            assertThat(labelSize.value?.height).isGreaterThan(0)
+            assertThat(labelSize.value?.width).isGreaterThan(0)
+            // centered position
+            assertThat(labelPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            assertThat(labelPosition.value?.y).isEqualTo(
+                ((height.toIntPx() - labelSize.value!!.height) / 2f).roundToInt().toFloat()
+            )
+        }
+    }
+
+    @Test
+    fun testFilledTextField_labelPosition_whenFocused() {
+        val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
+        val baseline = Ref<Float>()
+        testRule.setMaterialContent {
+            Box {
+                FilledTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = "",
+                    onValueChange = {},
+                    label = {
+                        Text(text = "label", modifier = Modifier.onPositioned {
+                            labelPosition.value = it.globalPosition
+                            labelSize.value = it.size
+                            baseline.value =
+                                it[FirstBaseline]!!.toFloat() + labelPosition.value!!.y
+                        })
+                    }
+                )
+            }
+        }
+
+        // click to focus
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(labelSize.value).isNotNull()
+            assertThat(labelSize.value?.height).isGreaterThan(0)
+            assertThat(labelSize.value?.width).isGreaterThan(0)
+            // label's top position
+            assertThat(labelPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            assertThat(baseline.value).isEqualTo(
+                ExpectedBaselineOffset.toIntPx().toFloat()
+            )
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_labelPosition_whenFocused() {
+        val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
+
+        testRule.setMaterialContent {
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextfieldTag),
+                value = "",
+                onValueChange = {},
+                label = {
+                    Text(text = "label", modifier = Modifier.onPositioned {
+                        labelPosition.value = it.globalPosition
+                        labelSize.value = it.size
+                    })
+                }
+            )
+        }
+
+        // click to focus
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(labelSize.value).isNotNull()
+            assertThat(labelSize.value?.height).isGreaterThan(0)
+            assertThat(labelSize.value?.width).isGreaterThan(0)
+            // label's top position
+            assertThat(labelPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            assertThat(labelPosition.value?.y).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun testFilledTextField_labelPosition_whenInput() {
+        val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
+        val baseline = Ref<Float>()
+        testRule.setMaterialContent {
+            Box {
+                FilledTextField(
+                    value = "input",
+                    onValueChange = {},
+                    label = {
+                        Text(text = "label", modifier = Modifier.onPositioned {
+                            labelPosition.value = it.globalPosition
+                            labelSize.value = it.size
+                            baseline.value =
+                                it[FirstBaseline]!!.toFloat() + labelPosition.value!!.y
+                        })
+                    }
+                )
+            }
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(labelSize.value).isNotNull()
+            assertThat(labelSize.value?.height).isGreaterThan(0)
+            assertThat(labelSize.value?.width).isGreaterThan(0)
+            // label's top position
+            assertThat(labelPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            assertThat(baseline.value).isEqualTo(
+                ExpectedBaselineOffset.toIntPx().toFloat()
+            )
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_labelPosition_whenInput() {
+        val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            OutlinedTextField(
+                value = "input",
+                onValueChange = {},
+                label = {
+                    Text(text = "label", modifier = Modifier.onPositioned {
+                        labelPosition.value = it.globalPosition
+                        labelSize.value = it.size
+                    })
+                }
+            )
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(labelSize.value).isNotNull()
+            assertThat(labelSize.value?.height).isGreaterThan(0)
+            assertThat(labelSize.value?.width).isGreaterThan(0)
+            // label's top position
+            assertThat(labelPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            assertThat(labelPosition.value?.y).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun testFilledTextField_placeholderPosition_withLabel() {
+        val placeholderSize = Ref<IntSize>()
+        val placeholderPosition = Ref<Offset>()
+        val placeholderBaseline = Ref<Float>()
+        testRule.setMaterialContent {
+            Box {
+                FilledTextField(
+                    modifier = Modifier
+                        .preferredHeight(60.dp)
+                        .testTag(TextfieldTag),
+                    value = "",
+                    onValueChange = {},
+                    label = { Text("label") },
+                    placeholder = {
+                        Text(text = "placeholder", modifier = Modifier.onPositioned {
+                            placeholderPosition.value = it.globalPosition
+                            placeholderSize.value = it.size
+                            placeholderBaseline.value =
+                                it[FirstBaseline]!!.toFloat() + placeholderPosition.value!!.y
+                        })
+                    }
+                )
+            }
+        }
+        // click to focus
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(placeholderSize.value).isNotNull()
+            assertThat(placeholderSize.value?.height).isGreaterThan(0)
+            assertThat(placeholderSize.value?.width).isGreaterThan(0)
+            // placeholder's position
+            assertThat(placeholderPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            assertThat(placeholderBaseline.value)
+                .isEqualTo(
+                    60.dp.toIntPx().toFloat() -
+                            ExpectedLastBaselineOffset.toIntPx().toFloat()
+                )
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_placeholderPosition_withLabel() {
+        val placeholderSize = Ref<IntSize>()
+        val placeholderPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Box {
+                OutlinedTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = "",
+                    onValueChange = {},
+                    label = { Text("label") },
+                    placeholder = {
+                        Text(text = "placeholder", modifier = Modifier.onPositioned {
+                            placeholderPosition.value = it.globalPosition
+                            placeholderSize.value = it.size
+                        })
+                    }
+                )
+            }
+        }
+        // click to focus
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(placeholderSize.value).isNotNull()
+            assertThat(placeholderSize.value?.height).isGreaterThan(0)
+            assertThat(placeholderSize.value?.width).isGreaterThan(0)
+            // placeholder's position
+            assertThat(placeholderPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            // placeholder is centered in 56.dp default container,
+            // plus additional 8.dp padding on top
+            assertThat(placeholderPosition.value?.y).isEqualTo(
+                ((ExpectedMinimumTextFieldHeight.toIntPx() - placeholderSize.value!!.height) /
+                        2f).roundToInt() +
+                        8.dp.toIntPx()
+            )
+        }
+    }
+
+    @Test
+    fun testFilledTextField_placeholderPosition_whenNoLabel() {
+        val placeholderSize = Ref<IntSize>()
+        val placeholderPosition = Ref<Offset>()
+        val placeholderBaseline = Ref<Float>()
+        val height = 60.dp
+        testRule.setMaterialContent {
+            Box {
+                FilledTextField(
+                    modifier = Modifier.preferredHeight(height).testTag(TextfieldTag),
+                    value = "",
+                    onValueChange = {},
+                    label = {},
+                    placeholder = {
+                        Text(text = "placeholder", modifier = Modifier.onPositioned {
+                            placeholderPosition.value = it.positionInRoot
+                            placeholderSize.value = it.size
+                            placeholderBaseline.value =
+                                it[FirstBaseline]!!.toFloat() + placeholderPosition.value!!.y
+                        })
+                    }
+                )
+            }
+        }
+        // click to focus
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(placeholderSize.value).isNotNull()
+            assertThat(placeholderSize.value?.height).isGreaterThan(0)
+            assertThat(placeholderSize.value?.width).isGreaterThan(0)
+            // centered position
+            assertThat(placeholderPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            assertThat(placeholderPosition.value?.y).isEqualTo(
+                ((height.toIntPx() - placeholderSize.value!!.height) / 2f).roundToInt().toFloat()
+            )
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_placeholderPosition_whenNoLabel() {
+        val placeholderSize = Ref<IntSize>()
+        val placeholderPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Box {
+                OutlinedTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = "",
+                    onValueChange = {},
+                    label = {},
+                    placeholder = {
+                        Text(text = "placeholder", modifier = Modifier.onPositioned {
+                            placeholderPosition.value = it.globalPosition
+                            placeholderSize.value = it.size
+                        })
+                    }
+                )
+            }
+        }
+        // click to focus
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        testRule.runOnIdleComposeWithDensity {
+            // size
+            assertThat(placeholderSize.value).isNotNull()
+            assertThat(placeholderSize.value?.height).isGreaterThan(0)
+            assertThat(placeholderSize.value?.width).isGreaterThan(0)
+            // centered position
+            assertThat(placeholderPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+            // placeholder is centered in 56.dp default container,
+            // plus additional 8.dp padding on top
+            assertThat(placeholderPosition.value?.y).isEqualTo(
+                ((ExpectedMinimumTextFieldHeight.toIntPx() - placeholderSize.value!!.height) /
+                        2f).roundToInt() +
+                        8.dp.toIntPx()
+            )
+        }
+    }
+
+    @Test
+    fun testFilledTextField_noPlaceholder_whenInputNotEmpty() {
+        val placeholderSize = Ref<IntSize>()
+        val placeholderPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Column {
+                FilledTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = "input",
+                    onValueChange = {},
+                    label = {},
+                    placeholder = {
+                        Text(text = "placeholder", modifier = Modifier.onPositioned {
+                            placeholderPosition.value = it.globalPosition
+                            placeholderSize.value = it.size
+                        })
+                    }
+                )
+            }
+        }
+
+        // click to focus
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        testRule.runOnIdleComposeWithDensity {
+            assertThat(placeholderSize.value).isNull()
+            assertThat(placeholderPosition.value).isNull()
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_noPlaceholder_whenInputNotEmpty() {
+        val placeholderSize = Ref<IntSize>()
+        val placeholderPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Column {
+                OutlinedTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = "input",
+                    onValueChange = {},
+                    label = {},
+                    placeholder = {
+                        Text(text = "placeholder", modifier = Modifier.onPositioned {
+                            placeholderPosition.value = it.globalPosition
+                            placeholderSize.value = it.size
+                        })
+                    }
+                )
+            }
+        }
+
+        // click to focus
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        testRule.runOnIdleComposeWithDensity {
+            assertThat(placeholderSize.value).isNull()
+            assertThat(placeholderPosition.value).isNull()
+        }
+    }
+
+    @Test
+    fun testFilledTextField_placeholderColorAndTextStyle() {
+        testRule.setMaterialContent {
+            FilledTextField(
+                modifier = Modifier.testTag(TextfieldTag),
+                value = "",
+                onValueChange = {},
+                label = {},
+                placeholder = {
+                    Text("placeholder")
+                    assertThat(contentColor())
+                        .isEqualTo(MaterialTheme.colors.onSurface.copy(0.6f))
+                    assertThat(currentTextStyle()).isEqualTo(MaterialTheme.typography.subtitle1)
+                }
+            )
+        }
+
+        // click to focus
+        findByTag(TextfieldTag).doClick()
+    }
+
+    @Test
+    fun testOutlinedTextField_placeholderColorAndTextStyle() {
+        testRule.setMaterialContent {
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextfieldTag),
+                value = "",
+                onValueChange = {},
+                label = {},
+                placeholder = {
+                    Text("placeholder")
+                    assertThat(contentColor())
+                        .isEqualTo(MaterialTheme.colors.onSurface.copy(0.6f))
+                    assertThat(currentTextStyle()).isEqualTo(MaterialTheme.typography.subtitle1)
+                }
+            )
+        }
+
+        // click to focus
+        findByTag(TextfieldTag).doClick()
+    }
+
+    @Test
+    fun testFilledTextField_trailingAndLeading_sizeAndPosition() {
+        val textFieldHeight = 60.dp
+        val textFieldWidth = 300.dp
+        val size = 30.dp
+        val leadingPosition = Ref<Offset>()
+        val leadingSize = Ref<IntSize>()
+        val trailingPosition = Ref<Offset>()
+        val trailingSize = Ref<IntSize>()
+
+        testRule.setMaterialContent {
+            FilledTextField(
+                value = "text",
+                onValueChange = {},
+                modifier = Modifier.preferredSize(textFieldWidth, textFieldHeight),
+                label = {},
+                leadingIcon = {
+                    Box(Modifier.preferredSize(size).onPositioned {
+                        leadingPosition.value = it.globalPosition
+                        leadingSize.value = it.size
+                    })
+                },
+                trailingIcon = {
+                    Box(Modifier.preferredSize(size).onPositioned {
+                        trailingPosition.value = it.globalPosition
+                        trailingSize.value = it.size
+                    })
+                }
+            )
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            // leading
+            assertThat(leadingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
+            assertThat(leadingPosition.value?.x).isEqualTo(IconPadding.toIntPx().toFloat())
+            assertThat(leadingPosition.value?.y).isEqualTo(
+                ((textFieldHeight.toIntPx() - leadingSize.value!!.height) / 2f).roundToInt()
+                    .toFloat()
+            )
+            // trailing
+            assertThat(trailingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
+            assertThat(trailingPosition.value?.x).isEqualTo(
+                (textFieldWidth.toIntPx() - IconPadding.toIntPx() - trailingSize.value!!.width)
+                    .toFloat()
+            )
+            assertThat(trailingPosition.value?.y)
+                .isEqualTo(
+                    ((textFieldHeight.toIntPx() - trailingSize.value!!.height) / 2f)
+                        .roundToInt().toFloat()
+                )
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_trailingAndLeading_sizeAndPosition() {
+        val textFieldWidth = 300.dp
+        val size = 30.dp
+        val leadingPosition = Ref<Offset>()
+        val leadingSize = Ref<IntSize>()
+        val trailingPosition = Ref<Offset>()
+        val trailingSize = Ref<IntSize>()
+
+        testRule.setMaterialContent {
+            OutlinedTextField(
+                value = "text",
+                onValueChange = {},
+                modifier = Modifier.preferredWidth(textFieldWidth),
+                label = {},
+                leadingIcon = {
+                    Box(Modifier.preferredSize(size).onPositioned {
+                        leadingPosition.value = it.globalPosition
+                        leadingSize.value = it.size
+                    })
+                },
+                trailingIcon = {
+                    Box(Modifier.preferredSize(size).onPositioned {
+                        trailingPosition.value = it.globalPosition
+                        trailingSize.value = it.size
+                    })
+                }
+            )
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            val minimumHeight = ExpectedMinimumTextFieldHeight.toIntPx()
+            // leading
+            assertThat(leadingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
+            assertThat(leadingPosition.value?.x).isEqualTo(IconPadding.toIntPx().toFloat())
+            assertThat(leadingPosition.value?.y).isEqualTo(
+                ((minimumHeight - leadingSize.value!!.height) / 2f).roundToInt() + 8.dp.toIntPx()
+            )
+            // trailing
+            assertThat(trailingSize.value).isEqualTo(IntSize(size.toIntPx(), size.toIntPx()))
+            assertThat(trailingPosition.value?.x).isEqualTo(
+                (textFieldWidth.toIntPx() - IconPadding.toIntPx() - trailingSize.value!!.width)
+                    .toFloat()
+            )
+            assertThat(trailingPosition.value?.y).isEqualTo(
+                ((minimumHeight - trailingSize.value!!.height) / 2f).roundToInt() + 8.dp.toIntPx()
+            )
+        }
+    }
+
+    @Test
+    fun testFilledTextField_labelPositionX_initial_withTrailingAndLeading() {
+        val height = 60.dp
+        val iconSize = 30.dp
+        val labelPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Box {
+                FilledTextField(
+                    value = "",
+                    onValueChange = {},
+                    modifier = Modifier.preferredHeight(height),
+                    label = {
+                        Text(text = "label", modifier = Modifier.onPositioned {
+                            labelPosition.value = it.globalPosition
+                        })
+                    },
+                    trailingIcon = { Box(Modifier.preferredSize(iconSize)) },
+                    leadingIcon = { Box(Modifier.preferredSize(iconSize)) }
+                )
+            }
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            assertThat(labelPosition.value?.x).isEqualTo(
+                (ExpectedPadding.toIntPx() + IconPadding.toIntPx() + iconSize.toIntPx())
+                    .toFloat()
+            )
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_labelPositionX_initial_withTrailingAndLeading() {
+        val iconSize = 30.dp
+        val labelPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Box {
+                OutlinedTextField(
+                    value = "",
+                    onValueChange = {},
+                    label = {
+                        Text(text = "label", modifier = Modifier.onPositioned {
+                            labelPosition.value = it.globalPosition
+                        })
+                    },
+                    trailingIcon = { Box(Modifier.preferredSize(iconSize)) },
+                    leadingIcon = { Box(Modifier.preferredSize(iconSize)) }
+                )
+            }
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            assertThat(labelPosition.value?.x).isEqualTo(
+                (ExpectedPadding.toIntPx() + IconPadding.toIntPx() + iconSize.toIntPx())
+                    .toFloat()
+            )
+        }
+    }
+
+    @Test
+    fun testFilledTextField_labelPositionX_initial_withEmptyTrailingAndLeading() {
+        val height = 60.dp
+        val labelPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Box {
+                FilledTextField(
+                    value = "",
+                    onValueChange = {},
+                    modifier = Modifier.preferredHeight(height),
+                    label = {
+                        Text(text = "label", modifier = Modifier.onPositioned {
+                            labelPosition.value = it.globalPosition
+                        })
+                    },
+                    trailingIcon = {},
+                    leadingIcon = {}
+                )
+            }
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            assertThat(labelPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_labelPositionX_initial_withEmptyTrailingAndLeading() {
+        val labelPosition = Ref<Offset>()
+        testRule.setMaterialContent {
+            Box {
+                OutlinedTextField(
+                    value = "",
+                    onValueChange = {},
+                    label = {
+                        Text(text = "label", modifier = Modifier.onPositioned {
+                            labelPosition.value = it.globalPosition
+                        })
+                    },
+                    trailingIcon = {},
+                    leadingIcon = {}
+                )
+            }
+        }
+
+        testRule.runOnIdleComposeWithDensity {
+            assertThat(labelPosition.value?.x).isEqualTo(
+                ExpectedPadding.toIntPx().toFloat()
+            )
+        }
+    }
+
+    @Test
+    fun testTextField_colorInLeadingTrailing_whenValidInput() {
+        testRule.setMaterialContent {
+            Column {
+                FilledTextField(
+                    value = "",
+                    onValueChange = {},
+                    label = {},
+                    isErrorValue = false,
+                    leadingIcon = {
+                        assertThat(contentColor())
+                            .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+                    },
+                    trailingIcon = {
+                        assertThat(contentColor())
+                            .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+                    }
+                )
+                OutlinedTextField(
+                    value = "",
+                    onValueChange = {},
+                    label = {},
+                    isErrorValue = false,
+                    leadingIcon = {
+                        assertThat(contentColor())
+                            .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+                    },
+                    trailingIcon = {
+                        assertThat(contentColor())
+                            .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+                    }
+                )
+            }
+        }
+    }
+
+    @Test
+    fun testTextField_colorInLeadingTrailing_whenInvalidInput() {
+        testRule.setMaterialContent {
+            Column {
+                FilledTextField(
+                    value = "",
+                    onValueChange = {},
+                    label = {},
+                    isErrorValue = true,
+                    leadingIcon = {
+                        assertThat(contentColor())
+                            .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+                    },
+                    trailingIcon = {
+                        assertThat(contentColor()).isEqualTo(MaterialTheme.colors.error)
+                    }
+                )
+                OutlinedTextField(
+                    value = "",
+                    onValueChange = {},
+                    label = {},
+                    isErrorValue = true,
+                    leadingIcon = {
+                        assertThat(contentColor())
+                            .isEqualTo(MaterialTheme.colors.onSurface.copy(IconColorAlpha))
+                    },
+                    trailingIcon = {
+                        assertThat(contentColor()).isEqualTo(MaterialTheme.colors.error)
+                    }
+                )
+            }
+        }
+    }
+
+    @Test
+    fun testFilledTextField_imeActionAndKeyboardTypePropagatedDownstream() {
+        val textInputService = mock<TextInputService>()
+        testRule.setContent {
+            Providers(
+                TextInputServiceAmbient provides textInputService
+            ) {
+                var text = state { TextFieldValue("") }
+                FilledTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = text.value,
+                    onValueChange = { text.value = it },
+                    label = {},
+                    imeAction = ImeAction.Go,
+                    keyboardType = KeyboardType.Email
+                )
+            }
+        }
+
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        runOnIdleCompose {
+            verify(textInputService, atLeastOnce()).startInput(
+                value = any(),
+                keyboardType = eq(KeyboardType.Email),
+                imeAction = eq(ImeAction.Go),
+                onEditCommand = any(),
+                onImeActionPerformed = any()
+            )
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_imeActionAndKeyboardTypePropagatedDownstream() {
+        val textInputService = mock<TextInputService>()
+        testRule.setContent {
+            Providers(
+                TextInputServiceAmbient provides textInputService
+            ) {
+                var text = state { TextFieldValue("") }
+                OutlinedTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = text.value,
+                    onValueChange = { text.value = it },
+                    label = {},
+                    imeAction = ImeAction.Go,
+                    keyboardType = KeyboardType.Email
+                )
+            }
+        }
+
+        clickAndAdvanceClock(TextfieldTag, 200)
+
+        runOnIdleCompose {
+            verify(textInputService, atLeastOnce()).startInput(
+                value = any(),
+                keyboardType = eq(KeyboardType.Email),
+                imeAction = eq(ImeAction.Go),
+                onEditCommand = any(),
+                onImeActionPerformed = any()
+            )
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun testFilledTextField_visualTransformationPropagated() {
+        testRule.setMaterialContent {
+            FilledTextField(
+                modifier = Modifier.testTag(TextfieldTag),
+                value = "qwerty",
+                onValueChange = {},
+                label = {},
+                visualTransformation = PasswordVisualTransformation('\u0020'),
+                backgroundColor = Color.White,
+                shape = RectangleShape
+            )
+        }
+
+        findByTag(TextfieldTag)
+            .captureToBitmap()
+            .assertShape(
+                density = testRule.density,
+                backgroundColor = Color.White,
+                shapeColor = Color.White,
+                shape = RectangleShape,
+                // avoid elevation artifacts
+                shapeOverlapPixelCount = with(testRule.density) { 3.dp.toPx() }
+            )
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun testOutlinedTextField_visualTransformationPropagated() {
+        testRule.setMaterialContent {
+            Box(Modifier.drawBackground(Color.White)) {
+                OutlinedTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = "qwerty",
+                    onValueChange = {},
+                    label = {},
+                    visualTransformation = PasswordVisualTransformation('\u0020')
+                )
+            }
+        }
+
+        findByTag(TextfieldTag)
+            .captureToBitmap()
+            .assertShape(
+                density = testRule.density,
+                backgroundColor = Color.White,
+                shapeColor = Color.White,
+                shape = RectangleShape,
+                // avoid elevation artifacts
+                shapeOverlapPixelCount = with(testRule.density) { 3.dp.toPx() }
+            )
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun testFilledTextField_alphaNotSet_toBackgroundColorAndTransparentColors() {
+        val latch = CountDownLatch(1)
+
+        testRule.setMaterialContent {
+            Stack(Modifier.drawBackground(Color.White)) {
+                FilledTextField(
+                    modifier = Modifier.testTag(TextfieldTag),
+                    value = "",
+                    onValueChange = {},
+                    label = {},
+                    shape = RectangleShape,
+                    backgroundColor = Color.Blue,
+                    activeColor = Color.Transparent,
+                    inactiveColor = Color.Transparent,
+                    onFocusChange = { focused ->
+                        if (focused) latch.countDown()
+                    }
+                )
+            }
+        }
+
+        val expectedColor = Color.Blue.copy(alpha = 0.12f).compositeOver(Color.White)
+
+        findByTag(TextfieldTag)
+            .captureToBitmap()
+            .assertShape(
+                density = testRule.density,
+                backgroundColor = Color.White,
+                shapeColor = expectedColor,
+                shape = RectangleShape,
+                // avoid elevation artifacts
+                shapeOverlapPixelCount = with(testRule.density) { 1.dp.toPx() }
+            )
+
+        findByTag(TextfieldTag).doClick()
+        assert(latch.await(1, TimeUnit.SECONDS))
+
+        findByTag(TextfieldTag)
+            .captureToBitmap()
+            .assertShape(
+                density = testRule.density,
+                backgroundColor = Color.White,
+                shapeColor = expectedColor,
+                shape = RectangleShape,
+                // avoid elevation artifacts
+                shapeOverlapPixelCount = with(testRule.density) { 1.dp.toPx() }
+            )
+    }
+
+    @Test
+    fun testFilledTextField_onTextInputStartedCallback() {
+        var controller: SoftwareKeyboardController? = null
+
+        testRule.setMaterialContent {
+            FilledTextField(
+                modifier = Modifier.testTag(TextfieldTag),
+                value = "",
+                onValueChange = {},
+                label = {},
+                onTextInputStarted = {
+                    controller = it
+                }
+            )
+        }
+        assertThat(controller).isNull()
+
+        findByTag(TextfieldTag)
+            .doClick()
+
+        runOnIdleCompose {
+            assertThat(controller).isNotNull()
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_onTextInputStartedCallback() {
+        var controller: SoftwareKeyboardController? = null
+
+        testRule.setMaterialContent {
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextfieldTag),
+                value = "",
+                onValueChange = {},
+                label = {},
+                onTextInputStarted = {
+                    controller = it
+                }
+            )
+        }
+        assertThat(controller).isNull()
+
+        findByTag(TextfieldTag)
+            .doClick()
+
+        runOnIdleCompose {
+            assertThat(controller).isNotNull()
+        }
+    }
+
+    @Test
+    fun testFilledTextField_imeActionCallback_withSoftwareKeyboardController() {
+        var controller: SoftwareKeyboardController? = null
+
+        testRule.setMaterialContent {
+            FilledTextField(
+                modifier = Modifier.testTag(TextfieldTag),
+                value = "",
+                onValueChange = {},
+                label = {},
+                imeAction = ImeAction.Go,
+                onImeActionPerformed = { _, softwareKeyboardController ->
+                    controller = softwareKeyboardController
+                }
+            )
+        }
+        assertThat(controller).isNull()
+
+        findByTag(TextfieldTag)
+            .doSendImeAction()
+
+        runOnIdleCompose {
+            assertThat(controller).isNotNull()
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_imeActionCallback_withSoftwareKeyboardController() {
+        var controller: SoftwareKeyboardController? = null
+
+        testRule.setMaterialContent {
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextfieldTag),
+                value = "",
+                onValueChange = {},
+                label = {},
+                imeAction = ImeAction.Go,
+                onImeActionPerformed = { _, softwareKeyboardController ->
+                    controller = softwareKeyboardController
+                }
+            )
+        }
+        assertThat(controller).isNull()
+
+        findByTag(TextfieldTag)
+            .doSendImeAction()
+
+        runOnIdleCompose {
+            assertThat(controller).isNotNull()
+        }
+    }
+
+    @Test
+    fun testTextField_scrollable_withLongInput() {
+        val scrollerPosition = TextFieldScrollerPosition()
+        testRule.setContent {
+            Stack {
+                TextFieldScroller(
+                    remember { scrollerPosition },
+                    Modifier.fillMaxWidth().preferredHeight(40.dp)
+                ) {
+                    TextField(
+                        value = TextFieldValue(LONG_TEXT),
+                        onValueChange = {}
+                    )
+                }
+            }
+        }
+
+        runOnIdleCompose {
+            assertThat(scrollerPosition.maximum).isLessThan(Float.POSITIVE_INFINITY)
+            assertThat(scrollerPosition.maximum).isGreaterThan(0f)
+        }
+    }
+
+    @Test
+    fun testTextField_notScrollable_withShortInput() {
+        val text = "text"
+        val scrollerPosition = TextFieldScrollerPosition()
+        testRule.setContent {
+            Stack {
+                TextFieldScroller(
+                    remember { scrollerPosition },
+                    Modifier.fillMaxWidth().preferredHeight(100.dp)
+                ) {
+                    TextField(
+                        value = TextFieldValue(text),
+                        onValueChange = {}
+                    )
+                }
+            }
+        }
+
+        runOnIdleCompose {
+            assertThat(scrollerPosition.maximum).isEqualTo(0f)
+        }
+    }
+
+    @Test
+    fun testTextField_scrolledAndClipped() {
+        val scrollerPosition = TextFieldScrollerPosition()
+
+        val parentSize = 200
+        val textFieldSize = 50
+
+        with(testRule.density) {
+            testRule.setContent {
+                Stack(
+                    Modifier
+                        .preferredSize(parentSize.toDp())
+                        .drawBackground(Color.White)
+                        .testTag(TextfieldTag)
+                ) {
+                    TextFieldScroller(
+                        remember { scrollerPosition },
+                        Modifier.preferredSize(textFieldSize.toDp())
+                    ) {
+                        TextField(
+                            value = TextFieldValue(LONG_TEXT),
+                            onValueChange = {}
+                        )
+                    }
+                }
+            }
+        }
+
+        runOnIdleCompose {}
+
+        findByTag(TextfieldTag)
+            .captureToBitmap()
+            .assertPixels(expectedSize = IntSize(parentSize, parentSize)) { position ->
+                if (position.x > textFieldSize && position.y > textFieldSize) Color.White else null
+            }
+    }
+
+    @Test
+    fun testTextField_swipe_whenLongInput() {
+        val scrollerPosition = TextFieldScrollerPosition()
+
+        testRule.setContent {
+            Stack {
+                TextFieldScroller(
+                    remember { scrollerPosition },
+                    Modifier.fillMaxWidth().preferredHeight(40.dp).testTag(TextfieldTag)
+                ) {
+                    TextField(
+                        value = TextFieldValue(LONG_TEXT),
+                        onValueChange = {}
+                    )
+                }
+            }
+        }
+
+        runOnIdleCompose {
+            assertThat(scrollerPosition.current).isEqualTo(0f)
+        }
+
+        findByTag(TextfieldTag)
+            .doGesture { sendSwipeDown() }
+
+        val firstSwipePosition = runOnIdleCompose {
+            scrollerPosition.current
+        }
+        assertThat(firstSwipePosition).isGreaterThan(0f)
+
+        findByTag(TextfieldTag)
+            .doGesture { sendSwipeUp() }
+        runOnIdleCompose {
+            assertThat(scrollerPosition.current).isLessThan(firstSwipePosition)
+        }
+    }
+
+    @Test
+    fun textFieldScroller_restoresScrollerPosition() {
+        val restorationTester = StateRestorationTester(testRule)
+        var scrollerPosition = TextFieldScrollerPosition()
+
+        restorationTester.setContent {
+            scrollerPosition = rememberSavedInstanceState(
+                saver = TextFieldScrollerPosition.Saver
+            ) {
+                TextFieldScrollerPosition()
+            }
+            TextFieldScroller(
+                scrollerPosition,
+                Modifier.fillMaxWidth().preferredHeight(40.dp).testTag(TextfieldTag)
+            ) {
+                TextField(
+                    value = TextFieldValue(LONG_TEXT),
+                    onValueChange = {}
+                )
+            }
+        }
+
+        findByTag(TextfieldTag)
+            .doGesture { sendSwipeDown() }
+
+        val swipePosition = runOnIdleCompose {
+            scrollerPosition.current
+        }
+        assertThat(swipePosition).isGreaterThan(0f)
+
+        runOnIdleCompose {
+            scrollerPosition = TextFieldScrollerPosition()
+            assertThat(scrollerPosition.current).isEqualTo(0f)
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        runOnIdleCompose {
+            assertThat(scrollerPosition.current).isEqualTo(swipePosition)
+        }
+    }
+
+    private fun clickAndAdvanceClock(tag: String, time: Long) {
+        findByTag(tag).doClick()
+        waitForIdle()
+        testRule.clockTestRule.pauseClock()
+        testRule.clockTestRule.advanceClock(time)
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/TextField.kt b/ui/ui-material/src/main/java/androidx/ui/material/TextField.kt
index e0befcc..df4c566 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/TextField.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/TextField.kt
@@ -63,9 +63,12 @@
 import androidx.ui.foundation.gestures.DragDirection
 import androidx.ui.foundation.gestures.ScrollableState
 import androidx.ui.foundation.gestures.scrollable
+import androidx.ui.foundation.drawBackground
 import androidx.ui.foundation.shape.corner.ZeroCornerSize
 import androidx.ui.geometry.Offset
 import androidx.ui.graphics.Color
+import androidx.ui.graphics.Path
+import androidx.ui.graphics.RectangleShape
 import androidx.ui.graphics.Shape
 import androidx.ui.graphics.drawscope.Stroke
 import androidx.ui.input.ImeAction
@@ -90,8 +93,8 @@
 import kotlin.math.roundToInt
 
 /**
- * Material Design implementation of the
- * [Material Filled TextField](https://material.io/components/text-fields/#filled-text-field)
+ * Material Design implementation of a
+ * [Filled TextField](https://material.io/components/text-fields/#filled-text-field)
  *
  * A simple example looks like:
  *
@@ -122,14 +125,14 @@
  * @sample androidx.ui.material.samples.TextFieldWithHideKeyboardOnImeAction
  *
  * If apart from input text change you also want to observe the cursor location or selection range,
- * use a FilledTextField overload with the [TextFieldValue] parameter instead.
+ * use the FilledTextField overload with the [TextFieldValue] parameter instead.
  *
  * @param value the input text to be shown in the text field
  * @param onValueChange the callback that is triggered when the input service updates the text. An
  * updated text comes as a parameter of the callback
  * @param label the label to be displayed inside the text field container. The default text style
  * for internal [Text] is [Typography.caption] when the text field is in focus and
- * [Typography.subtitle1] when text field is not in focus
+ * [Typography.subtitle1] when the text field is not in focus
  * @param modifier a [Modifier] for this text field
  * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
  * [currentTextStyle] defined by the theme
@@ -139,7 +142,6 @@
  * container
  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
  * container
- * If the boolean parameter value is `true`, it means the text field has a focus, and vice versa
  * @param isErrorValue indicates if the text field's current value is in error. If set to true, the
  * label, bottom indicator and trailing icon will be displayed in [errorColor] color
  * @param keyboardType the keyboard type to be used with the text field.
@@ -156,15 +158,16 @@
  * @param visualTransformation transforms the visual representation of the input [value].
  * For example, you can use [androidx.ui.input.PasswordVisualTransformation] to create a password
  * text field. By default no visual transformation is applied
- * @param onFocusChange a callback to be invoked when the text field gets or loses the focus
+ * @param onFocusChange a callback to be invoked when the text field receives or loses focus
+ * If the boolean parameter value is `true`, it means the text field has focus, and vice versa
  * @param onTextInputStarted a callback to be invoked when the connection with the platform's text
  * input service (e.g. software keyboard on Android) has been established. Called with the
  * [SoftwareKeyboardController] instance that can be used to request to show or hide the software
  * keyboard
  * @param activeColor the color of the label, bottom indicator and the cursor when the text field is
  * in focus
- * @param inactiveColor the color of the input text or placeholder when the text field is in
- * focus, and the color of label and bottom indicator when the text field is not in focus
+ * @param inactiveColor the color of either the input text or placeholder when the text field is in
+ * focus, and the color of the label and bottom indicator when the text field is not in focus
  * @param errorColor the alternative color of the label, bottom indicator, cursor and trailing icon
  * used when [isErrorValue] is set to true
  * @param backgroundColor the background color of the text field's container. To the color provided
@@ -203,7 +206,8 @@
         )
         textFieldValue = TextFieldValue(text = value, selection = newSelection)
     }
-    FilledTextFieldImpl(
+    TextFieldImpl(
+        type = TextFieldType.Filled,
         value = textFieldValue,
         onValueChange = {
             val previousValue = textFieldValue.text
@@ -234,14 +238,14 @@
 }
 
 /**
- * Material Design implementation of the
- * [Material Filled TextField](https://material.io/components/text-fields/#filled-text-field)
- *
+ * Material Design implementation of a
+ * [Filled TextField](https://material.io/components/text-fields/#filled-text-field)
  *
  * See example usage:
  * @sample androidx.ui.material.samples.FilledTextFieldSample
  *
- * If you only want to observe an input text change, use a FilledTextField overload with the
+ * This overload provides access to the input text, cursor position and selection range. If you
+ * only want to observe an input text change, use the FilledTextField overload with the
  * [String] parameter instead.
  *
  * @param value the input [TextFieldValue] to be shown in the text field
@@ -249,7 +253,7 @@
  * selection or cursor. An updated [TextFieldValue] comes as a parameter of the callback
  * @param label the label to be displayed inside the text field container. The default text style
  * for internal [Text] is [Typography.caption] when the text field is in focus and
- * [Typography.subtitle1] when text field is not in focus
+ * [Typography.subtitle1] when the text field is not in focus
  * @param modifier a [Modifier] for this text field
  * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
  * [currentTextStyle] defined by the theme
@@ -259,7 +263,6 @@
  * container
  * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
  * container
- * If the boolean parameter value is `true`, it means the text field has a focus, and vice versa
  * @param isErrorValue indicates if the text field's current value is in error state. If set to
  * true, the label, bottom indicator and trailing icon will be displayed in [errorColor] color
  * @param keyboardType the keyboard type to be used with the text field.
@@ -276,15 +279,16 @@
  * @param visualTransformation transforms the visual representation of the input [value].
  * For example, you can use [androidx.ui.input.PasswordVisualTransformation] to create a password
  * text field. By default no visual transformation is applied
- * @param onFocusChange a callback to be invoked when the text field gets or loses the focus
+ * @param onFocusChange a callback to be invoked when the text field receives or loses focus
+ * If the boolean parameter value is `true`, it means the text field has focus, and vice versa
  * @param onTextInputStarted a callback to be invoked when the connection with the platform's text
  * input service (e.g. software keyboard on Android) has been established. Called with the
  * [SoftwareKeyboardController] instance that can be used to request to show or hide the software
  * keyboard
  * @param activeColor the color of the label, bottom indicator and the cursor when the text field is
  * in focus
- * @param inactiveColor the color of the input text or placeholder when the text field is in
- * focus, and the color of label and bottom indicator when the text field is not in focus
+ * @param inactiveColor the color of either the input text or placeholder when the text field is in
+ * focus, and the color of the label and bottom indicator when the text field is not in focus
  * @param errorColor the alternative color of the label, bottom indicator, cursor and trailing icon
  * used when [isErrorValue] is set to true
  * @param backgroundColor the background color of the text field's container. To the color provided
@@ -315,7 +319,8 @@
     shape: Shape =
         MaterialTheme.shapes.small.copy(bottomLeft = ZeroCornerSize, bottomRight = ZeroCornerSize)
 ) {
-    FilledTextFieldImpl(
+    TextFieldImpl(
+        type = TextFieldType.Filled,
         value = value,
         onValueChange = onValueChange,
         modifier = modifier,
@@ -390,7 +395,8 @@
             )
         }
     }
-    FilledTextFieldImpl(
+    TextFieldImpl(
+        type = TextFieldType.Filled,
         value = fullModel.value,
         onValueChange = onValueChangeWrapper,
         modifier = modifier,
@@ -415,10 +421,231 @@
 }
 
 /**
- * Implementation of the [FilledTextField]
+ * Material Design implementation of an
+ * [Outlined TextField](https://material.io/components/text-fields/#outlined-text-field)
+ *
+ * See example usage:
+ * @sample androidx.ui.material.samples.SimpleOutlinedTextFieldSample
+ *
+ * If apart from input text change you also want to observe the cursor location or selection range,
+ * use the OutlinedTextField overload with the [TextFieldValue] parameter instead.
+ *
+ * @param value the input text to be shown in the text field
+ * @param onValueChange the callback that is triggered when the input service updates the text. An
+ * updated text comes as a parameter of the callback
+ * @param label the label to be displayed inside the text field container. The default text style
+ * for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus
+ * @param modifier a [Modifier] for this text field
+ * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
+ * [currentTextStyle] defined by the theme
+ * @param placeholder the optional placeholder to be displayed when the text field is in focus and
+ * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
+ * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
+ * container
+ * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
+ * container
+ * @param isErrorValue indicates if the text field's current value is in error. If set to true, the
+ * label, bottom indicator and trailing icon will be displayed in [errorColor] color
+ * @param keyboardType the keyboard type to be used with the text field.
+ * Note that the input type is not guaranteed. For example, an IME may send a non-ASCII character
+ * even if you set the keyboard type to [KeyboardType.Ascii]
+ * @param imeAction the IME action honored by the IME. The 'enter' key on the soft keyboard input
+ * will show a corresponding icon. For example, search icon may be shown if [ImeAction.Search] is
+ * selected. When a user taps on that 'enter' key, the [onImeActionPerformed] callback is called
+ * with the specified [ImeAction]
+ * @param onImeActionPerformed is triggered when the input service performs an [ImeAction].
+ * Note that the emitted IME action may be different from what you specified through the
+ * [imeAction] field. The callback also exposes a [SoftwareKeyboardController] instance as a
+ * parameter that can be used to request to hide the software keyboard
+ * @param visualTransformation transforms the visual representation of the input [value].
+ * For example, you can use [androidx.ui.input.PasswordVisualTransformation] to create a password
+ * text field. By default no visual transformation is applied
+ * @param onFocusChange a callback to be invoked when the text field receives or loses focus
+ * If the boolean parameter value is `true`, it means the text field has focus, and vice versa
+ * @param onTextInputStarted a callback to be invoked when the connection with the platform's text
+ * input service (e.g. software keyboard on Android) has been established. Called with the
+ * [SoftwareKeyboardController] instance that can be used to request to show or hide the software
+ * keyboard
+ * @param activeColor the color of the label, bottom indicator and the cursor when the text field is
+ * in focus
+ * @param inactiveColor the color of either the input text or placeholder when the text field is in
+ * focus, and the color of the label and bottom indicator when the text field is not in focus
+ * @param errorColor the alternative color of the label, bottom indicator, cursor and trailing icon
+ * used when [isErrorValue] is set to true
  */
 @Composable
-private fun FilledTextFieldImpl(
+fun OutlinedTextField(
+    value: String,
+    onValueChange: (String) -> Unit,
+    label: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    textStyle: TextStyle = currentTextStyle(),
+    placeholder: @Composable (() -> Unit)? = null,
+    leadingIcon: @Composable (() -> Unit)? = null,
+    trailingIcon: @Composable (() -> Unit)? = null,
+    isErrorValue: Boolean = false,
+    visualTransformation: VisualTransformation = VisualTransformation.None,
+    keyboardType: KeyboardType = KeyboardType.Text,
+    imeAction: ImeAction = ImeAction.Unspecified,
+    onImeActionPerformed: (ImeAction, SoftwareKeyboardController?) -> Unit = { _, _ -> },
+    onFocusChange: (Boolean) -> Unit = {},
+    onTextInputStarted: (SoftwareKeyboardController) -> Unit = {},
+    activeColor: Color = MaterialTheme.colors.primary,
+    inactiveColor: Color = MaterialTheme.colors.onSurface,
+    errorColor: Color = MaterialTheme.colors.error
+) {
+    var textFieldValue by state { TextFieldValue() }
+    if (textFieldValue.text != value) {
+        val newSelection = TextRange(
+            textFieldValue.selection.start.coerceIn(0, value.length),
+            textFieldValue.selection.end.coerceIn(0, value.length)
+        )
+        textFieldValue = TextFieldValue(text = value, selection = newSelection)
+    }
+
+    TextFieldImpl(
+        type = TextFieldType.Outlined,
+        value = textFieldValue,
+        onValueChange = {
+            val previousValue = textFieldValue.text
+            textFieldValue = it
+            if (previousValue != it.text) {
+                onValueChange(it.text)
+            }
+        },
+        modifier = modifier,
+        textStyle = textStyle,
+        label = label,
+        placeholder = placeholder,
+        leading = leadingIcon,
+        trailing = trailingIcon,
+        isErrorValue = isErrorValue,
+        visualTransformation = visualTransformation,
+        keyboardType = keyboardType,
+        imeAction = imeAction,
+        onImeActionPerformed = onImeActionPerformed,
+        onFocusChange = onFocusChange,
+        onTextInputStarted = onTextInputStarted,
+        activeColor = activeColor,
+        inactiveColor = inactiveColor,
+        errorColor = errorColor,
+        backgroundColor = Color.Unset,
+        shape = RectangleShape
+    )
+}
+
+/**
+ * Material Design implementation of an
+ * [Outlined TextField](https://material.io/components/text-fields/#outlined-text-field)
+ *
+ * See example usage:
+ * @sample androidx.ui.material.samples.OutlinedTextFieldSample
+ *
+ * This overload provides access to the input text, cursor position and selection range. If you
+ * only want to observe an input text change, use the OutlinedTextField overload with the
+ * [String] parameter instead.
+ *
+ * @param value the input [TextFieldValue] to be shown in the text field
+ * @param onValueChange the callback that is triggered when the input service updates the text,
+ * selection or cursor. An updated [TextFieldValue] comes as a parameter of the callback
+ * @param label the label to be displayed inside the text field container. The default text style
+ * for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus
+ * @param modifier a [Modifier] for this text field
+ * @param textStyle the style to be applied to the input text. The default [textStyle] uses the
+ * [currentTextStyle] defined by the theme
+ * @param placeholder the optional placeholder to be displayed when the text field is in focus and
+ * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
+ * @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
+ * container
+ * @param trailingIcon the optional trailing icon to be displayed at the end of the text field
+ * container
+ * @param isErrorValue indicates if the text field's current value is in error state. If set to
+ * true, the label, bottom indicator and trailing icon will be displayed in [errorColor] color
+ * @param keyboardType the keyboard type to be used with the text field.
+ * Note that the input type is not guaranteed. For example, an IME may send a non-ASCII character
+ * even if you set the keyboard type to [KeyboardType.Ascii]
+ * @param imeAction the IME action honored by the IME. The 'enter' key on the soft keyboard input
+ * will show a corresponding icon. For example, search icon may be shown if [ImeAction.Search] is
+ * selected. When a user taps on that 'enter' key, the [onImeActionPerformed] callback is called
+ * with the specified [ImeAction]
+ * @param onImeActionPerformed is triggered when the input service performs an [ImeAction].
+ * Note that the emitted IME action may be different from what you specified through the
+ * [imeAction] field. The callback also exposes a [SoftwareKeyboardController] instance as a
+ * parameter that can be used to request to hide the software keyboard
+ * @param visualTransformation transforms the visual representation of the input [value].
+ * For example, you can use [androidx.ui.input.PasswordVisualTransformation] to create a password
+ * text field. By default no visual transformation is applied
+ * @param onFocusChange a callback to be invoked when the text field receives or loses focus
+ * If the boolean parameter value is `true`, it means the text field has focus, and vice versa
+ * @param onTextInputStarted a callback to be invoked when the connection with the platform's text
+ * input service (e.g. software keyboard on Android) has been established. Called with the
+ * [SoftwareKeyboardController] instance that can be used to request to show or hide the software
+ * keyboard
+ * @param activeColor the color of the label, bottom indicator and the cursor when the text field is
+ * in focus
+ * @param inactiveColor the color of either the input text or placeholder when the text field is in
+ * focus, and the color of the label and bottom indicator when the text field is not in focus
+ * @param errorColor the alternative color of the label, bottom indicator, cursor and trailing icon
+ * used when [isErrorValue] is set to true
+ */
+@Composable
+fun OutlinedTextField(
+    value: TextFieldValue,
+    onValueChange: (TextFieldValue) -> Unit,
+    label: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    textStyle: TextStyle = currentTextStyle(),
+    placeholder: @Composable (() -> Unit)? = null,
+    leadingIcon: @Composable (() -> Unit)? = null,
+    trailingIcon: @Composable (() -> Unit)? = null,
+    isErrorValue: Boolean = false,
+    visualTransformation: VisualTransformation = VisualTransformation.None,
+    keyboardType: KeyboardType = KeyboardType.Text,
+    imeAction: ImeAction = ImeAction.Unspecified,
+    onImeActionPerformed: (ImeAction, SoftwareKeyboardController?) -> Unit = { _, _ -> },
+    onFocusChange: (Boolean) -> Unit = {},
+    onTextInputStarted: (SoftwareKeyboardController) -> Unit = {},
+    activeColor: Color = MaterialTheme.colors.primary,
+    inactiveColor: Color = MaterialTheme.colors.onSurface,
+    errorColor: Color = MaterialTheme.colors.error
+) {
+    TextFieldImpl(
+        type = TextFieldType.Outlined,
+        value = value,
+        onValueChange = onValueChange,
+        modifier = modifier,
+        textStyle = textStyle,
+        label = label,
+        placeholder = placeholder,
+        leading = leadingIcon,
+        trailing = trailingIcon,
+        isErrorValue = isErrorValue,
+        visualTransformation = visualTransformation,
+        keyboardType = keyboardType,
+        imeAction = imeAction,
+        onImeActionPerformed = onImeActionPerformed,
+        onFocusChange = onFocusChange,
+        onTextInputStarted = onTextInputStarted,
+        activeColor = activeColor,
+        inactiveColor = inactiveColor,
+        errorColor = errorColor,
+        backgroundColor = Color.Unset,
+        shape = RectangleShape
+    )
+}
+
+private enum class TextFieldType {
+    Filled, Outlined
+}
+
+/**
+ * Implementation of the [FilledTextField] and [OutlinedTextField]
+ */
+@Composable
+private fun TextFieldImpl(
+    type: TextFieldType,
     value: TextFieldValue,
     onValueChange: (TextFieldValue) -> Unit,
     modifier: Modifier,
@@ -479,7 +706,7 @@
             ) {
                 TextField(
                     value = value,
-                    modifier = tagModifier + focusModifier,
+                    modifier = focusModifier,
                     textStyle = textStyle,
                     onValueChange = onValueChange,
                     onFocusChange = onFocusChange,
@@ -499,84 +726,194 @@
         }
     }
 
-    val textFieldModifier = Modifier
-        .preferredSizeIn(
-            minWidth = TextFieldMinWidth,
-            minHeight = TextFieldMinHeight
-        )
-        .plus(modifier)
+    val textFieldModifier = modifier
         .semantics(mergeAllDescendants = true)
+        .clickable(indication = RippleIndication(bounded = false)) {
+            focusModifier.requestFocus()
+            keyboardController.value?.showSoftwareKeyboard()
+        }
 
-    Surface(
-        modifier = textFieldModifier,
-        shape = shape,
-        color = backgroundColor.applyAlpha(alpha = ContainerAlpha)
-    ) {
-        val emphasisLevels = EmphasisAmbient.current
-        val emphasizedActiveColor = emphasisLevels.high.applyEmphasis(activeColor)
-        val labelInactiveColor = emphasisLevels.medium.applyEmphasis(inactiveColor)
-        val indicatorInactiveColor =
-            inactiveColor.applyAlpha(alpha = IndicatorInactiveAlpha)
+    val emphasisLevels = EmphasisAmbient.current
 
-        TextFieldTransitionScope.transition(
-            inputState = inputState.value,
-            activeColor = emphasizedActiveColor,
-            labelInactiveColor = labelInactiveColor,
-            indicatorInactiveColor = indicatorInactiveColor
-        ) { labelProgress, animatedLabelColor, indicatorWidth, indicatorColor ->
-            // TODO(soboleva): figure out how this will play with the textStyle provided in label slot
+    TextFieldTransitionScope.transition(
+        inputState = inputState.value,
+        activeColor = if (isErrorValue) {
+            errorColor
+        } else {
+            emphasisLevels.high.applyEmphasis(activeColor)
+        },
+        labelInactiveColor = emphasisLevels.medium.applyEmphasis(inactiveColor),
+        indicatorInactiveColor = inactiveColor.applyAlpha(alpha = IndicatorInactiveAlpha)
+    ) { labelProgress, animatedLabelColor, indicatorWidth, animatedIndicatorColor ->
+
+        val leadingColor = inactiveColor.applyAlpha(alpha = TrailingLeadingAlpha)
+        val trailingColor = if (isErrorValue) errorColor else leadingColor
+
+        val decoratedLabel = @Composable {
             val labelAnimatedStyle = lerp(
                 MaterialTheme.typography.subtitle1,
                 MaterialTheme.typography.caption,
                 labelProgress
             )
-
-            val leadingColor = inactiveColor.applyAlpha(alpha = TrailingLeadingAlpha)
-            val trailingColor = if (isErrorValue) errorColor else leadingColor
-
-            // text field with label and placeholder
-            val labelColor = if (isErrorValue) errorColor else animatedLabelColor
-            val decoratedLabel = @Composable {
-                Decoration(
-                    contentColor = labelColor,
-                    typography = labelAnimatedStyle,
-                    children = label
-                )
-            }
-
-            // places leading icon, text field with label, trailing icon
-            IconsTextFieldLayout(
-                modifier = Modifier
-                    .clickable(indication = RippleIndication(bounded = false)) {
-                        focusModifier.requestFocus()
-                        keyboardController.value?.showSoftwareKeyboard()
-                    }
-                    .drawIndicatorLine(
-                        lineWidth = indicatorWidth,
-                        color = if (isErrorValue) errorColor else indicatorColor
-                    ),
-                textField = {
-                    TextFieldLayout(
-                        animationProgress = labelProgress,
-                        modifier = Modifier
-                            .padding(
-                                start = HorizontalTextFieldPadding,
-                                end = HorizontalTextFieldPadding
-                            ),
-                        placeholder = decoratedPlaceholder,
-                        label = decoratedLabel,
-                        textField = decoratedTextField
-                    )
-                },
-                leading = leading,
-                trailing = trailing,
-                leadingColor = leadingColor,
-                trailingColor = trailingColor
+            Decoration(
+                contentColor = animatedLabelColor,
+                typography = labelAnimatedStyle,
+                children = label
             )
         }
+
+        when (type) {
+            TextFieldType.Filled -> {
+                FilledTextFieldLayout(
+                    textFieldModifier = Modifier
+                        .preferredSizeIn(
+                            minWidth = TextFieldMinWidth,
+                            minHeight = TextFieldMinHeight
+                        )
+                        .plus(textFieldModifier),
+                    decoratedTextField = decoratedTextField,
+                    decoratedPlaceholder = decoratedPlaceholder,
+                    decoratedLabel = decoratedLabel,
+                    leading = leading,
+                    trailing = trailing,
+                    leadingColor = leadingColor,
+                    trailingColor = trailingColor,
+                    labelProgress = labelProgress,
+                    indicatorWidth = indicatorWidth,
+                    indicatorColor = animatedIndicatorColor,
+                    backgroundColor = backgroundColor,
+                    shape = shape
+                )
+            }
+            TextFieldType.Outlined -> {
+                OutlinedTextFieldLayout(
+                    textFieldModifier = Modifier
+                        .preferredSizeIn(
+                            minWidth = TextFieldMinWidth,
+                            minHeight = TextFieldMinHeight + OutlinedTextFieldTopPadding
+                        )
+                        .plus(textFieldModifier)
+                        .padding(top = OutlinedTextFieldTopPadding),
+                    decoratedTextField = decoratedTextField,
+                    decoratedPlaceholder = decoratedPlaceholder,
+                    decoratedLabel = decoratedLabel,
+                    leading = leading,
+                    trailing = trailing,
+                    leadingColor = leadingColor,
+                    trailingColor = trailingColor,
+                    labelProgress = labelProgress,
+                    indicatorWidth = indicatorWidth,
+                    indicatorColor = animatedIndicatorColor,
+                    focusModifier = focusModifier,
+                    emptyInput = value.text.isEmpty()
+                )
+            }
+        }
     }
 }
 
+@Composable
+private fun FilledTextFieldLayout(
+    textFieldModifier: Modifier = Modifier,
+    decoratedTextField: @Composable (Modifier) -> Unit,
+    decoratedPlaceholder: @Composable (() -> Unit)?,
+    decoratedLabel: @Composable () -> Unit,
+    leading: @Composable (() -> Unit)?,
+    trailing: @Composable (() -> Unit)?,
+    leadingColor: Color,
+    trailingColor: Color,
+    labelProgress: Float,
+    indicatorWidth: Dp,
+    indicatorColor: Color,
+    backgroundColor: Color,
+    shape: Shape
+) {
+    // places leading icon, text field with label and placeholder, trailing icon
+    FilledTextField.IconsWithTextFieldLayout(
+        modifier = textFieldModifier
+            .drawBackground(
+                color = backgroundColor.applyAlpha(alpha = ContainerAlpha),
+                shape = shape
+            )
+            .drawIndicatorLine(
+                lineWidth = indicatorWidth,
+                color = indicatorColor
+            ),
+        textField = @Composable {
+            // places input field, label, placeholder
+            FilledTextField.TextFieldWithLabelLayout(
+                animationProgress = labelProgress,
+                modifier = Modifier
+                    .padding(
+                        start = TextFieldPadding,
+                        end = TextFieldPadding
+                    ),
+                placeholder = decoratedPlaceholder,
+                label = decoratedLabel,
+                textField = decoratedTextField
+            )
+        },
+        leading = leading,
+        trailing = trailing,
+        leadingColor = leadingColor,
+        trailingColor = trailingColor
+    )
+}
+
+@Composable
+private fun OutlinedTextFieldLayout(
+    textFieldModifier: Modifier = Modifier,
+    decoratedTextField: @Composable (Modifier) -> Unit,
+    decoratedPlaceholder: @Composable (() -> Unit)?,
+    decoratedLabel: @Composable () -> Unit,
+    leading: @Composable (() -> Unit)?,
+    trailing: @Composable (() -> Unit)?,
+    leadingColor: Color,
+    trailingColor: Color,
+    labelProgress: Float,
+    indicatorWidth: Dp,
+    indicatorColor: Color,
+    focusModifier: FocusModifier,
+    emptyInput: Boolean
+) {
+    val outlinedBorderParams = remember {
+        OutlinedBorderParams(indicatorWidth, indicatorColor)
+    }
+    if (indicatorColor != outlinedBorderParams.color.value ||
+        indicatorWidth != outlinedBorderParams.borderWidth.value
+    ) {
+        outlinedBorderParams.color.value = indicatorColor
+        outlinedBorderParams.borderWidth.value = indicatorWidth
+    }
+
+    // places leading icon, input field, label, placeholder, trailing icon
+    OutlinedTextField.IconsWithTextFieldLayout(
+        modifier = textFieldModifier.drawOutlinedBorder(outlinedBorderParams),
+        textField = decoratedTextField,
+        leading = leading,
+        trailing = trailing,
+        leadingColor = leadingColor,
+        trailingColor = trailingColor,
+        onLabelMeasured = {
+            val newLabelWidth = it * labelProgress
+
+            val labelWidth = when {
+                focusModifier.focusState == FocusState.Focused -> newLabelWidth
+                !emptyInput -> newLabelWidth
+                focusModifier.focusState == FocusState.NotFocused && emptyInput -> newLabelWidth
+                else -> 0f
+            }
+
+            if (outlinedBorderParams.labelWidth.value != labelWidth) {
+                outlinedBorderParams.labelWidth.value = labelWidth
+            }
+        },
+        animationProgress = labelProgress,
+        placeholder = decoratedPlaceholder,
+        label = decoratedLabel
+    )
+}
+
 /**
  * Similar to [androidx.ui.foundation.VerticalScroller] but does not lose the minWidth constraints.
  */
@@ -662,152 +999,444 @@
     if (typography != null) ProvideTextStyle(typography, colorAndEmphasis) else colorAndEmphasis()
 }
 
-/**
- * Layout of the text field, label and placeholder
- */
-@Composable
-private fun TextFieldLayout(
-    animationProgress: Float,
-    modifier: Modifier,
-    placeholder: @Composable (() -> Unit)?,
-    label: @Composable () -> Unit,
-    textField: @Composable (Modifier) -> Unit
-) {
-    Layout(
-        children = {
-            if (placeholder != null) {
-                Box(modifier = Modifier.tag(PlaceholderTag), children = placeholder)
+private object FilledTextField {
+    /**
+     * Layout of the text field, label and placeholder part in [FilledTextField]
+     */
+    @Composable
+    fun TextFieldWithLabelLayout(
+        animationProgress: Float,
+        modifier: Modifier,
+        placeholder: @Composable (() -> Unit)?,
+        label: @Composable () -> Unit,
+        textField: @Composable (Modifier) -> Unit
+    ) {
+        Layout(
+            children = {
+                if (placeholder != null) {
+                    Box(modifier = Modifier.tag(PlaceholderTag), children = placeholder)
+                }
+                Box(modifier = Modifier.tag(LabelTag), children = label)
+                textField(Modifier.tag(TextFieldTag))
+            },
+            modifier = modifier
+        ) { measurables, constraints, _ ->
+            val placeholderPlaceable =
+                measurables.find { it.tag == PlaceholderTag }?.measure(constraints)
+
+            val baseLineOffset = FirstBaselineOffset.toIntPx()
+
+            val labelConstraints = constraints
+                .offset(vertical = -LastBaselineOffset.toIntPx())
+                .copy(minWidth = 0, minHeight = 0)
+            val labelPlaceable = measurables.first { it.tag == LabelTag }.measure(labelConstraints)
+            val labelBaseline = labelPlaceable[LastBaseline] ?: labelPlaceable.height
+            val labelEndPosition = (baseLineOffset - labelBaseline).coerceAtLeast(0)
+            val effectiveLabelBaseline = max(labelBaseline, baseLineOffset)
+
+            val textFieldConstraints = constraints
+                .offset(vertical = -LastBaselineOffset.toIntPx() - effectiveLabelBaseline)
+                .copy(minHeight = 0)
+            val textfieldPlaceable = measurables
+                .first { it.tag == TextFieldTag }
+                .measure(textFieldConstraints)
+            val textfieldLastBaseline = requireNotNull(textfieldPlaceable[LastBaseline]) {
+                "No text last baseline."
             }
-            Box(modifier = Modifier.tag(LabelTag), children = label)
-            textField(Modifier.tag(TextFieldTag))
-        },
-        modifier = modifier
-    ) { measurables, constraints, _ ->
-        val placeholderPlaceable =
-            measurables.find { it.tag == PlaceholderTag }?.measure(constraints)
 
-        val baseLineOffset = FirstBaselineOffset.toIntPx()
+            val width = max(textfieldPlaceable.width, constraints.minWidth)
+            val height = max(
+                effectiveLabelBaseline + textfieldPlaceable.height + LastBaselineOffset.toIntPx(),
+                constraints.minHeight
+            )
 
-        val labelConstraints = constraints
-            .offset(vertical = -LastBaselineOffset.toIntPx())
-            .copy(minWidth = 0, minHeight = 0)
-        val labelPlaceable = measurables.first { it.tag == LabelTag }.measure(labelConstraints)
-        val labelBaseline = labelPlaceable[LastBaseline] ?: labelPlaceable.height
-        val labelEndPosition = (baseLineOffset - labelBaseline).coerceAtLeast(0)
-        val effectiveLabelBaseline = max(labelBaseline, baseLineOffset)
-
-        val textFieldConstraints = constraints
-            .offset(vertical = -LastBaselineOffset.toIntPx() - effectiveLabelBaseline)
-            .copy(minHeight = 0)
-        val textfieldPlaceable = measurables
-            .first { it.tag == TextFieldTag }
-            .measure(textFieldConstraints)
-        val textfieldLastBaseline = requireNotNull(textfieldPlaceable[LastBaseline]) {
-            "No text last baseline."
-        }
-
-        val width = max(textfieldPlaceable.width, constraints.minWidth)
-        val height = max(
-            effectiveLabelBaseline + textfieldPlaceable.height + LastBaselineOffset.toIntPx(),
-            constraints.minHeight
-        )
-
-        layout(width, height) {
-            // Text field and label are placed with respect to the baseline offsets.
-            // But if label is empty, then the text field should be centered vertically.
-            if (labelPlaceable.width != 0) {
-                // only respects the offset from the last baseline to the bottom of the text field
-                val textfieldPositionY = height - LastBaselineOffset.toIntPx() -
-                        min(textfieldLastBaseline, textfieldPlaceable.height)
-                placeLabelAndTextfield(
-                    width,
-                    height,
-                    textfieldPlaceable,
-                    labelPlaceable,
-                    placeholderPlaceable,
-                    labelEndPosition,
-                    textfieldPositionY,
-                    animationProgress
-                )
-            } else {
-                placeTextfield(width, height, textfieldPlaceable, placeholderPlaceable)
+            layout(width, height) {
+                // Text field and label are placed with respect to the baseline offsets.
+                // But if label is empty, then the text field should be centered vertically.
+                if (labelPlaceable.width != 0) {
+                    // only respects the offset from the last baseline to the bottom of the text field
+                    val textfieldPositionY = height - LastBaselineOffset.toIntPx() -
+                            min(textfieldLastBaseline, textfieldPlaceable.height)
+                    placeLabelAndTextfield(
+                        width,
+                        height,
+                        textfieldPlaceable,
+                        labelPlaceable,
+                        placeholderPlaceable,
+                        labelEndPosition,
+                        textfieldPositionY,
+                        animationProgress
+                    )
+                } else {
+                    placeTextfield(width, height, textfieldPlaceable, placeholderPlaceable)
+                }
             }
         }
     }
+
+    /**
+     * Layout of the leading and trailing icons and the text field part in [FilledTextField].
+     * It differs from the Row as it does not lose the minHeight constraint which is needed to
+     * correctly place the text field and label.
+     * Should be revisited if b/154202249 is fixed so that Row could be used instead
+     */
+    @Composable
+    fun IconsWithTextFieldLayout(
+        modifier: Modifier = Modifier,
+        textField: @Composable () -> Unit,
+        leading: @Composable (() -> Unit)?,
+        trailing: @Composable (() -> Unit)?,
+        leadingColor: Color,
+        trailingColor: Color
+    ) {
+        Layout(
+            children = {
+                if (leading != null) {
+                    Box(Modifier.tag("leading").iconPadding(start = HorizontalIconPadding)) {
+                        Decoration(contentColor = leadingColor, children = leading)
+                    }
+                }
+                if (trailing != null) {
+                    Box(Modifier.tag("trailing").iconPadding(end = HorizontalIconPadding)) {
+                        Decoration(contentColor = trailingColor, children = trailing)
+                    }
+                }
+                textField()
+            },
+            modifier = modifier
+        ) { measurables, incomingConstraints, _ ->
+            val constraints =
+                incomingConstraints.copy(minWidth = 0, minHeight = 0)
+            var occupiedSpace = 0
+
+            val leadingPlaceable = measurables.find { it.tag == "leading" }?.measure(constraints)
+            occupiedSpace += widthOrZero(leadingPlaceable)
+
+            val trailingPlaceable = measurables.find { it.tag == "trailing" }
+                ?.measure(constraints.offset(horizontal = -occupiedSpace))
+            occupiedSpace += widthOrZero(trailingPlaceable)
+
+            // represents the layout that holds textfield, label and placeholder
+            val textFieldPlaceable = measurables.first {
+                it.tag != "leading" && it.tag != "trailing"
+            }.measure(incomingConstraints.offset(horizontal = -occupiedSpace))
+            occupiedSpace += textFieldPlaceable.width
+
+            val width = max(occupiedSpace, incomingConstraints.minWidth)
+            val height = max(
+                heightOrZero(
+                    listOf(
+                        leadingPlaceable,
+                        trailingPlaceable,
+                        textFieldPlaceable
+                    ).maxBy { heightOrZero(it) }
+                ),
+                incomingConstraints.minHeight
+            )
+            layout(width, height) {
+                leadingPlaceable?.place(
+                    0,
+                    Alignment.CenterVertically.align(height - leadingPlaceable.height)
+                )
+                textFieldPlaceable.place(
+                    leadingPlaceable?.width ?: 0,
+                    Alignment.CenterVertically.align(height - textFieldPlaceable.height)
+                )
+                trailingPlaceable?.place(
+                    width - trailingPlaceable.width,
+                    Alignment.CenterVertically.align(height - trailingPlaceable.height)
+                )
+            }
+        }
+    }
+
+    /**
+     * Places the provided text field, placeholder and label with respect to the baseline offsets in
+     * [FilledTextField]
+     */
+    private fun Placeable.PlacementScope.placeLabelAndTextfield(
+        width: Int,
+        height: Int,
+        textfieldPlaceable: Placeable,
+        labelPlaceable: Placeable,
+        placeholderPlaceable: Placeable?,
+        labelEndPosition: Int,
+        textPosition: Int,
+        animationProgress: Float
+    ) {
+        val labelCenterPosition = Alignment.CenterStart.align(
+            IntSize(
+                width - labelPlaceable.width,
+                height - labelPlaceable.height
+            )
+        )
+        val labelDistance = labelCenterPosition.y - labelEndPosition
+        val labelPositionY =
+            labelCenterPosition.y - (labelDistance * animationProgress).roundToInt()
+        labelPlaceable.place(0, labelPositionY)
+
+        textfieldPlaceable.place(0, textPosition)
+        placeholderPlaceable?.place(0, textPosition)
+    }
+
+    /**
+     * Places the provided text field and placeholder center vertically in [FilledTextField]
+     */
+    private fun Placeable.PlacementScope.placeTextfield(
+        width: Int,
+        height: Int,
+        textPlaceable: Placeable,
+        placeholderPlaceable: Placeable?
+    ) {
+        val textCenterPosition = Alignment.CenterStart.align(
+            IntSize(
+                width - textPlaceable.width,
+                height - textPlaceable.height
+            )
+        )
+        textPlaceable.place(0, textCenterPosition.y)
+        placeholderPlaceable?.place(0, textCenterPosition.y)
+    }
 }
 
-/**
- * Layout of the leading and trailing icons and the text field.
- * It differs from the Row as it does not lose the minHeight constraint which is needed to
- * correctly place the text field and label.
- * Should be revisited if b/154202249 is fixed so that Row could be used instead
- */
-@Composable
-private fun IconsTextFieldLayout(
-    modifier: Modifier = Modifier,
-    textField: @Composable () -> Unit,
-    leading: @Composable (() -> Unit)?,
-    trailing: @Composable (() -> Unit)?,
-    leadingColor: Color,
-    trailingColor: Color
-) {
-    Layout(
-        children = {
-            if (leading != null) {
-                Box(Modifier.tag("leading").iconPadding(start = HorizontalIconPadding)) {
-                    Decoration(contentColor = leadingColor, children = leading)
+private object OutlinedTextField {
+    /**
+     * Layout of the leading and trailing icons and the text field, label and placeholder in
+     * [OutlinedTextField].
+     * It doesn't use Row to position the icons and middle part because label should not be
+     * positioned in the middle part.
+    \ */
+    @Composable
+    fun IconsWithTextFieldLayout(
+        modifier: Modifier = Modifier,
+        textField: @Composable (Modifier) -> Unit,
+        placeholder: @Composable (() -> Unit)?,
+        label: @Composable () -> Unit,
+        leading: @Composable (() -> Unit)?,
+        trailing: @Composable (() -> Unit)?,
+        leadingColor: Color,
+        trailingColor: Color,
+        animationProgress: Float,
+        onLabelMeasured: (Int) -> Unit
+    ) {
+        Layout(
+            children = {
+                if (leading != null) {
+                    Box(Modifier.tag("leading").iconPadding(start = HorizontalIconPadding)) {
+                        Decoration(contentColor = leadingColor, children = leading)
+                    }
                 }
-            }
-            if (trailing != null) {
-                Box(Modifier.tag("trailing").iconPadding(end = HorizontalIconPadding)) {
-                    Decoration(contentColor = trailingColor, children = trailing)
+                if (trailing != null) {
+                    Box(Modifier.tag("trailing").iconPadding(end = HorizontalIconPadding)) {
+                        Decoration(contentColor = trailingColor, children = trailing)
+                    }
                 }
-            }
-            textField()
-        },
-        modifier = modifier
-    ) { measurables, incomingConstraints, _ ->
-        val constraints = incomingConstraints.copy(minWidth = 0, minHeight = 0)
-        var occupiedSpace = 0
+                if (placeholder != null) {
+                    Box(
+                        modifier = Modifier
+                            .tag(PlaceholderTag)
+                            .padding(horizontal = TextFieldPadding),
+                        children = placeholder
+                    )
+                }
 
-        val leadingPlaceable = measurables.find { it.tag == "leading" }?.measure(constraints)
-        occupiedSpace += leadingPlaceable?.width ?: 0
+                textField(
+                    Modifier
+                        .tag(TextFieldTag)
+                        .padding(horizontal = TextFieldPadding)
+                )
 
-        val trailingPlaceable = measurables.find { it.tag == "trailing" }
-            ?.measure(constraints.offset(horizontal = -occupiedSpace))
-        occupiedSpace += trailingPlaceable?.width ?: 0
+                Box(modifier = Modifier.tag(LabelTag), children = label)
+            },
+            modifier = modifier
+        ) { measurables, incomingConstraints, _ ->
+            // used to calculate the constraints for measuring elements that will be placed in a row
+            var occupiedSpaceHorizontally = 0
+            val bottomPadding = TextFieldPadding.toIntPx()
 
-        val textFieldPlaceable = measurables.first {
-            it.tag != "leading" && it.tag != "trailing"
-        }.measure(incomingConstraints.offset(horizontal = -occupiedSpace))
-        occupiedSpace += textFieldPlaceable.width
+            // measure leading icon
+            val constraints =
+                incomingConstraints.copy(minWidth = 0, minHeight = 0)
+            val leadingPlaceable = measurables.find { it.tag == "leading" }?.measure(constraints)
+            occupiedSpaceHorizontally += widthOrZero(leadingPlaceable)
 
-        val width = max(occupiedSpace, incomingConstraints.minWidth)
-        val height = max(
-            listOf(
+            // measure trailing icon
+            val trailingPlaceable = measurables.find { it.tag == "trailing" }
+                ?.measure(constraints.offset(horizontal = -occupiedSpaceHorizontally))
+            occupiedSpaceHorizontally += widthOrZero(trailingPlaceable)
+
+            // measure label
+            val labelConstraints = constraints.offset(
+                horizontal = -occupiedSpaceHorizontally,
+                vertical = -bottomPadding
+            )
+            val labelPlaceable =
+                measurables.first { it.tag == LabelTag }.measure(labelConstraints)
+            onLabelMeasured(labelPlaceable.width)
+
+            // measure text field
+            // on top we offset either by default padding or by label's half height if its too big
+            // minWidth must not be set to 0 due to how foundation TextField treats zero minWidth
+            val topPadding = max(labelPlaceable.height / 2, bottomPadding)
+            val textContraints = incomingConstraints.offset(
+                horizontal = -occupiedSpaceHorizontally,
+                vertical = -bottomPadding - topPadding
+            ).copy(minHeight = 0)
+            val textFieldPlaceable =
+                measurables.first { it.tag == TextFieldTag }.measure(textContraints)
+
+            // measure placeholder
+            val placeholderConstraints = textContraints.copy(minWidth = 0)
+            val placeholderPlaceable =
+                measurables.find { it.tag == PlaceholderTag }?.measure(placeholderConstraints)
+
+            val width = calculateWidth(
                 leadingPlaceable,
                 trailingPlaceable,
-                textFieldPlaceable
-            ).maxBy { it?.height ?: 0 }?.height ?: 0,
-            incomingConstraints.minHeight
-        )
-        layout(width, height) {
-            leadingPlaceable?.place(
-                0,
-                Alignment.CenterVertically.align(height - leadingPlaceable.height)
+                textFieldPlaceable,
+                labelPlaceable,
+                placeholderPlaceable,
+                incomingConstraints
             )
-            textFieldPlaceable.place(
-                leadingPlaceable?.width ?: 0,
-                Alignment.CenterVertically.align(height - textFieldPlaceable.height)
+            val height = calculateHeight(
+                leadingPlaceable,
+                trailingPlaceable,
+                textFieldPlaceable,
+                labelPlaceable,
+                placeholderPlaceable,
+                incomingConstraints,
+                density
             )
-            trailingPlaceable?.place(
-                width - trailingPlaceable.width,
-                Alignment.CenterVertically.align(height - trailingPlaceable.height)
-            )
+            layout(width, height) {
+                place(
+                    height,
+                    width,
+                    leadingPlaceable,
+                    trailingPlaceable,
+                    textFieldPlaceable,
+                    labelPlaceable,
+                    placeholderPlaceable,
+                    animationProgress,
+                    density
+                )
+            }
         }
     }
+
+    /**
+     * Calculate the width of the [OutlinedTextField] given all elements that should be
+     * placed inside
+     */
+    private fun calculateWidth(
+        leadingPlaceable: Placeable?,
+        trailingPlaceable: Placeable?,
+        textFieldPlaceable: Placeable,
+        labelPlaceable: Placeable,
+        placeholderPlaceable: Placeable?,
+        constraints: Constraints
+    ): Int {
+        val middleSection = widthOrZero(
+            listOf(
+                textFieldPlaceable,
+                labelPlaceable,
+                placeholderPlaceable
+            ).maxBy { widthOrZero(it) }
+        )
+        val wrappedWidth =
+            widthOrZero(leadingPlaceable) + middleSection + widthOrZero(trailingPlaceable)
+        return max(wrappedWidth, constraints.minWidth)
+    }
+
+    /**
+     * Calculate the height of the [OutlinedTextField] given all elements that should be
+     * placed inside
+     */
+    private fun calculateHeight(
+        leadingPlaceable: Placeable?,
+        trailingPlaceable: Placeable?,
+        textFieldPlaceable: Placeable,
+        labelPlaceable: Placeable,
+        placeholderPlaceable: Placeable?,
+        constraints: Constraints,
+        density: Float
+    ): Int {
+        // middle section is defined as a height of the text field or placeholder ( whichever is
+        // taller) plus 16.dp or half height of the label if it is taller, given that the label
+        // is vertically centered to the top edge of the resulting text field's container
+        val inputFieldHeight = max(textFieldPlaceable.height, heightOrZero(placeholderPlaceable))
+        val topBottomPadding = TextFieldPadding.value * density
+        val middleSectionHeight = inputFieldHeight + topBottomPadding + max(
+            topBottomPadding,
+            labelPlaceable.height / 2f
+        )
+        return max(
+            listOf(
+                heightOrZero(leadingPlaceable),
+                heightOrZero(trailingPlaceable),
+                middleSectionHeight.roundToInt()
+            ).max() ?: 0,
+            constraints.minHeight
+        )
+    }
+
+    /**
+     * Places the provided text field, placeholder, label, optional leading and trailing icons inside
+     * the [OutlinedTextField]
+     */
+    private fun Placeable.PlacementScope.place(
+        height: Int,
+        width: Int,
+        leadingPlaceable: Placeable?,
+        trailingPlaceable: Placeable?,
+        textFieldPlaceable: Placeable,
+        labelPlaceable: Placeable,
+        placeholderPlaceable: Placeable?,
+        animationProgress: Float,
+        density: Float
+    ) {
+        // placed center vertically and to the start edge horizontally
+        leadingPlaceable?.place(
+            0,
+            Alignment.CenterVertically.align(height - leadingPlaceable.height)
+        )
+
+        // placed center vertically and to the end edge horizontally
+        trailingPlaceable?.place(
+            width - trailingPlaceable.width,
+            Alignment.CenterVertically.align(height - trailingPlaceable.height)
+        )
+
+        // if animation progress is 0, the label will be centered vertically
+        // if animation progress is 1, vertically it will be centered to the container's top edge
+        // horizontally it is placed after the leading icon
+        val labelPositionY =
+            Alignment.CenterVertically.align(height - labelPlaceable.height) * (1 -
+                    animationProgress) - (labelPlaceable.height / 2) * animationProgress
+        val labelPositionX = (TextFieldPadding.value * density) +
+                widthOrZero(leadingPlaceable) * (1 - animationProgress)
+        labelPlaceable.place(labelPositionX.roundToInt(), labelPositionY.roundToInt())
+
+        // placed center vertically and after the leading icon horizontally
+        textFieldPlaceable.place(
+            widthOrZero(leadingPlaceable),
+            Alignment.CenterVertically.align(height - textFieldPlaceable.height)
+        )
+
+        // placed center vertically and after the leading icon horizontally
+        placeholderPlaceable?.place(
+            widthOrZero(leadingPlaceable),
+            Alignment.CenterVertically.align(height - placeholderPlaceable.height)
+        )
+    }
 }
 
+private val Placeable.nonZero: Boolean get() = this.width != 0 || this.height != 0
+private fun widthOrZero(placeable: Placeable?) = placeable?.width ?: 0
+private fun heightOrZero(placeable: Placeable?) = placeable?.height ?: 0
+
+/**
+ * A modifier that applies padding only if the size of the element is not zero
+ */
 private fun Modifier.iconPadding(start: Dp = 0.dp, end: Dp = 0.dp) =
     this + object : LayoutModifier {
         override fun MeasureScope.measure(
@@ -828,10 +1457,8 @@
         }
     }
 
-private val Placeable.nonZero: Boolean get() = this.width != 0 || this.height != 0
-
 /**
- * A draw modifier that draws a bottom indicator line
+ * A draw modifier that draws a bottom indicator line in [FilledTextField]
  */
 private fun Modifier.drawIndicatorLine(lineWidth: Dp, color: Color): Modifier {
     return drawBehind {
@@ -847,50 +1474,75 @@
 }
 
 /**
- * Places the provided text field, placeholder and label with respect to the baseline offsets
+ * A draw modifier to draw a border line in [OutlinedTextField]
  */
-private fun Placeable.PlacementScope.placeLabelAndTextfield(
-    width: Int,
-    height: Int,
-    textfieldPlaceable: Placeable,
-    labelPlaceable: Placeable,
-    placeholderPlaceable: Placeable?,
-    labelEndPosition: Int,
-    textPosition: Int,
-    animationProgress: Float
-) {
-    val labelCenterPosition = Alignment.CenterStart.align(
-        IntSize(
-            width - labelPlaceable.width,
-            height - labelPlaceable.height
-        )
-    )
-    val labelDistance = labelCenterPosition.y - labelEndPosition
-    val labelPositionY =
-        labelCenterPosition.y - (labelDistance * animationProgress).roundToInt()
-    labelPlaceable.place(0, labelPositionY)
+private fun Modifier.drawOutlinedBorder(
+    borderParams: OutlinedBorderParams
+): Modifier = drawBehind {
+    val padding = TextFieldPadding.value * density
+    val innerPadding = OutlinedTextFieldInnerPadding.value * density
 
-    textfieldPlaceable.place(0, textPosition)
-    placeholderPlaceable?.place(0, textPosition)
+    val lineWidth = borderParams.borderWidth.value.value * density
+    val width: Float = size.width
+    val height: Float = size.height
+
+    val radius = borderParams.cornerRadius.value * density
+    val dx = if (radius > width / 2) width / 2 else radius
+    val dy = if (radius > height / 2) height / 2 else radius
+
+    val path = Path().apply {
+        // width and height minus corners and line width
+        val effectiveWidth: Float = width - 2 * dx - lineWidth
+        val effectiveHeight: Float = height - 2 * dy - lineWidth
+
+        // top-right corner
+        moveTo(width - lineWidth / 2, dy + lineWidth / 2)
+        relativeQuadraticBezierTo(0f, -dy, -dx, -dy)
+
+        // top line with gap
+        val diff = borderParams.labelWidth.value
+        if (diff == 0f) {
+            relativeLineTo(-effectiveWidth, 0f)
+        } else {
+            val effectivePadding = padding - innerPadding - dx - lineWidth / 2
+            val gap = diff + 2 * innerPadding
+            relativeLineTo(-effectiveWidth + effectivePadding + gap, 0f)
+            relativeMoveTo(-gap, 0f)
+            relativeLineTo(-effectivePadding, 0f)
+        }
+
+        // top-left corner and left line
+        relativeQuadraticBezierTo(-dx, 0f, -dx, dy)
+        relativeLineTo(0f, effectiveHeight)
+
+        // bottom-left corner and bottom line
+        relativeQuadraticBezierTo(0f, dy, dx, dy)
+        relativeLineTo(effectiveWidth, 0f)
+
+        // bottom-right corner and right line
+        relativeQuadraticBezierTo(dx, 0f, dx, -dy)
+        relativeLineTo(0f, -effectiveHeight)
+    }
+
+    drawPath(
+        path = path,
+        color = borderParams.color.value,
+        style = Stroke(width = lineWidth)
+    )
 }
 
 /**
- * Places the provided text field and placeholder center vertically
+ * A data class that stores parameters needed for [drawOutlinedBorder] modifier
  */
-private fun Placeable.PlacementScope.placeTextfield(
-    width: Int,
-    height: Int,
-    textPlaceable: Placeable,
-    placeholderPlaceable: Placeable?
+@Stable
+private class OutlinedBorderParams(
+    initialBorderWidth: Dp,
+    initialColor: Color
 ) {
-    val textCenterPosition = Alignment.CenterStart.align(
-        IntSize(
-            width - textPlaceable.width,
-            height - textPlaceable.height
-        )
-    )
-    textPlaceable.place(0, textCenterPosition.y)
-    placeholderPlaceable?.place(0, textCenterPosition.y)
+    val borderWidth = mutableStateOf(initialBorderWidth)
+    val color = mutableStateOf(initialColor)
+    val cornerRadius = OutlinedTextFieldCornerRadius
+    val labelWidth = mutableStateOf(0f)
 }
 
 private object TextFieldTransitionScope {
@@ -1016,5 +1668,16 @@
 private val TextFieldMinWidth = 280.dp
 private val FirstBaselineOffset = 20.dp
 private val LastBaselineOffset = 16.dp
-private val HorizontalTextFieldPadding = 16.dp
-private val HorizontalIconPadding = 12.dp
\ No newline at end of file
+private val TextFieldPadding = 16.dp
+private val HorizontalIconPadding = 12.dp
+private val OutlinedTextFieldInnerPadding = 4.dp
+
+// TODO(b/158077409) support shape in OutlinedTextField
+private val OutlinedTextFieldCornerRadius = 4.dp
+
+/*
+This padding is used to allow label not overlap with the content above it. This 8.dp will work
+for default cases when developers do not override the label's font size. If they do, they will
+need to add additional padding themselves
+*/
+private val OutlinedTextFieldTopPadding = 8.dp
\ No newline at end of file
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendDoubleClickTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendDoubleClickTest.kt
index 9225fd0..a96308c 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendDoubleClickTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendDoubleClickTest.kt
@@ -20,7 +20,8 @@
 import androidx.ui.core.Modifier
 import androidx.ui.core.gesture.doubleTapGestureFilter
 import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.doGesture
 import androidx.ui.test.findByTag
@@ -65,11 +66,8 @@
     @get:Rule
     val composeTestRule = createComposeRule(disableTransitions = true)
 
-    private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
-    private val eventPeriod get() = dispatcherRule.eventPeriod
-
     @get:Rule
-    val inputDispatcherRule: TestRule = dispatcherRule
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recordedDoubleClicks = mutableListOf<Offset>()
     private val expectedClickPosition =
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendPinchTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendPinchTest.kt
index 5d8638d..e28dac9 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendPinchTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendPinchTest.kt
@@ -21,7 +21,7 @@
 import androidx.ui.geometry.Offset
 import androidx.ui.layout.Stack
 import androidx.ui.layout.fillMaxSize
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.doGesture
 import androidx.ui.test.findByTag
@@ -49,7 +49,6 @@
     @get:Rule
     val composeTestRule = createComposeRule(disableTransitions = true)
 
-    private val eventPeriod = AndroidInputDispatcher.TestRule().eventPeriod
     private val recorder = MultiPointerInputRecorder()
 
     @Test
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt
index ed63f5f..2739718 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt
@@ -23,7 +23,7 @@
 import androidx.ui.layout.Stack
 import androidx.ui.layout.fillMaxSize
 import androidx.ui.layout.wrapContentSize
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.doGesture
 import androidx.ui.test.findByTag
@@ -121,7 +121,7 @@
     val composeTestRule = createComposeRule(disableTransitions = true)
 
     @get:Rule
-    val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(
         disableDispatchInRealTime = true,
         eventPeriodOverride = eventPeriod
     )
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/Common.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/Common.kt
index bcd8bb5..38e8ad6 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/Common.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/Common.kt
@@ -31,16 +31,24 @@
     assertThat(getCurrentPosition(pointerId)).isEqualTo(position)
 }
 
-internal fun AndroidInputDispatcher.sendUpAndCheck(pointerId: Int) {
-    sendUp(pointerId)
+internal fun AndroidInputDispatcher.sendUpAndCheck(pointerId: Int, delay: Long? = null) {
+    if (delay != null) {
+        sendUp(pointerId, delay)
+    } else {
+        sendUp(pointerId)
+    }
     assertThat(getCurrentPosition(pointerId)).isNull()
 }
 
-internal fun AndroidInputDispatcher.sendCancelAndCheck() {
-    sendCancel()
+internal fun AndroidInputDispatcher.sendCancelAndCheck(delay: Long? = null) {
+    if (delay != null) {
+        sendCancel(delay)
+    } else {
+        sendCancel()
+    }
     verifyNoGestureInProgress()
 }
 
 internal fun InputDispatcher.verifyNoGestureInProgress() {
-    assertThat(getState().partialGesture).isNull()
+    assertThat(isGestureInProgress).isFalse()
 }
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/DelayTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/DelayTest.kt
index d4b5038..6f22944b 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/DelayTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/DelayTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import androidx.ui.geometry.Offset
 import androidx.ui.test.InputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.unit.Duration
@@ -85,9 +86,7 @@
     }
 
     @get:Rule
-    val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
-        disableDispatchInRealTime = true
-    )
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MotionEventRecorder()
     private val subject = AndroidInputDispatcher(recorder::recordEvent)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/IsGestureInProgressTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/IsGestureInProgressTest.kt
new file mode 100644
index 0000000..3197ace
--- /dev/null
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/IsGestureInProgressTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test.inputdispatcher
+
+import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+
+class IsGestureInProgressTest {
+    companion object {
+        private val anyPosition = Offset.Zero
+    }
+
+    @get:Rule
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
+
+    private val subject = AndroidInputDispatcher {}
+
+    @Test
+    fun downUp() {
+        assertThat(subject.isGestureInProgress).isFalse()
+        subject.sendDown(1, anyPosition)
+        assertThat(subject.isGestureInProgress).isTrue()
+        subject.sendUp(1)
+        assertThat(subject.isGestureInProgress).isFalse()
+    }
+
+    @Test
+    fun downCancel() {
+        assertThat(subject.isGestureInProgress).isFalse()
+        subject.sendDown(1, anyPosition)
+        assertThat(subject.isGestureInProgress).isTrue()
+        subject.sendCancel()
+        assertThat(subject.isGestureInProgress).isFalse()
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendCancelTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendCancelTest.kt
index c7a6aba..7fd7f34 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendCancelTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendCancelTest.kt
@@ -21,6 +21,8 @@
 import android.view.MotionEvent.ACTION_POINTER_DOWN
 import androidx.test.filters.SmallTest
 import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.test.util.assertHasValidEventTimes
@@ -48,11 +50,8 @@
         private val position2_1 = Offset(21f, 21f)
     }
 
-    private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
-    private val eventPeriod get() = dispatcherRule.eventPeriod
-
     @get:Rule
-    val inputDispatcherRule: TestRule = dispatcherRule
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MotionEventRecorder()
     private val subject = AndroidInputDispatcher(recorder::recordEvent)
@@ -62,8 +61,8 @@
         recorder.disposeEvents()
     }
 
-    private fun AndroidInputDispatcher.sendCancelAndCheckPointers() {
-        sendCancelAndCheck()
+    private fun AndroidInputDispatcher.sendCancelAndCheckPointers(delay: Long? = null) {
+        sendCancelAndCheck(delay)
         assertThat(getCurrentPosition(pointer1)).isNull()
         assertThat(getCurrentPosition(pointer2)).isNull()
     }
@@ -88,6 +87,25 @@
     }
 
     @Test
+    fun onePointerWithDelay() {
+        subject.sendDownAndCheck(pointer1, position1_1)
+        subject.sendCancelAndCheckPointers(2 * eventPeriod)
+        subject.verifyNoGestureInProgress()
+        recorder.assertHasValidEventTimes()
+
+        recorder.events.apply {
+            var t = 0L
+            assertThat(this).hasSize(2)
+            this[0].verifyEvent(1, ACTION_DOWN, 0, t) // pointer1
+            this[0].verifyPointer(pointer1, position1_1)
+
+            t += 2 * eventPeriod
+            this[1].verifyEvent(1, ACTION_CANCEL, 0, t)
+            this[1].verifyPointer(pointer1, position1_1)
+        }
+    }
+
+    @Test
     fun multiplePointers() {
         subject.sendDownAndCheck(pointer1, position1_1)
         subject.sendDownAndCheck(pointer2, position2_1)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendClickTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendClickTest.kt
index 1ba9bfa..acb3ec0 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendClickTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendClickTest.kt
@@ -19,6 +19,8 @@
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.test.util.assertHasValidEventTimes
@@ -54,11 +56,8 @@
         }
     }
 
-    private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
-    private val eventPeriod get() = dispatcherRule.eventPeriod
-
     @get:Rule
-    val inputDispatcherRule: TestRule = dispatcherRule
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val position = Offset(config.x, config.y)
 
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendDownTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendDownTest.kt
index 81b0ea2..edd89c4 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendDownTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendDownTest.kt
@@ -22,6 +22,8 @@
 import android.view.MotionEvent.ACTION_POINTER_UP
 import androidx.test.filters.SmallTest
 import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.test.util.assertHasValidEventTimes
@@ -56,11 +58,8 @@
         private val position1_2 = Offset(12f, 12f)
     }
 
-    private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
-    private val eventPeriod get() = dispatcherRule.eventPeriod
-
     @get:Rule
-    val inputDispatcherRule: TestRule = dispatcherRule
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MotionEventRecorder()
     private val subject = AndroidInputDispatcher(recorder::recordEvent)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendMoveTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendMoveTest.kt
index ccfe985..fd31d50 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendMoveTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendMoveTest.kt
@@ -23,6 +23,8 @@
 import android.view.MotionEvent.ACTION_POINTER_UP
 import androidx.test.filters.SmallTest
 import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.test.util.assertHasValidEventTimes
@@ -57,11 +59,8 @@
         private val position1_3 = Offset(13f, 13f)
     }
 
-    private val dispatcherRule = AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
-    private val eventPeriod get() = dispatcherRule.eventPeriod
-
     @get:Rule
-    val inputDispatcherRule: TestRule = dispatcherRule
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MotionEventRecorder()
     private val subject = AndroidInputDispatcher(recorder::recordEvent)
@@ -96,6 +95,23 @@
     }
 
     @Test
+    fun onePointerWithDelay() {
+        subject.sendDownAndCheck(pointer1, position1_1)
+        subject.movePointerAndCheck(pointer1, position1_2)
+        subject.sendMove(2 * eventPeriod)
+
+        var t = 0L
+        recorder.assertHasValidEventTimes()
+        assertThat(recorder.events).hasSize(2)
+        recorder.events[0].verifyEvent(1, ACTION_DOWN, 0, t) // pointer1
+        recorder.events[0].verifyPointer(pointer1, position1_1)
+
+        t += 2 * eventPeriod
+        recorder.events[1].verifyEvent(1, ACTION_MOVE, 0, t) // pointer1
+        recorder.events[1].verifyPointer(pointer1, position1_2)
+    }
+
+    @Test
     fun twoPointers_downDownMoveMove() {
         // 2 fingers, both go down before they move
         subject.sendDownAndCheck(pointer1, position1_1)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeLineTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeLineTest.kt
index 0e83446..44bda8b 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeLineTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeLineTest.kt
@@ -19,6 +19,7 @@
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.test.util.assertHasValidEventTimes
@@ -64,7 +65,7 @@
     }
 
     @get:Rule
-    val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(
         disableDispatchInRealTime = true,
         eventPeriodOverride = config.eventPeriod
     )
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithDurationTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithDurationTest.kt
index b69b16a..9ea938f 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithDurationTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithDurationTest.kt
@@ -21,6 +21,7 @@
 import android.view.MotionEvent.ACTION_UP
 import androidx.test.filters.SmallTest
 import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.test.util.assertHasValidEventTimes
@@ -94,9 +95,7 @@
     }
 
     @get:Rule
-    val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
-        disableDispatchInRealTime = true
-    )
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MotionEventRecorder()
     private val subject = AndroidInputDispatcher(recorder::recordEvent)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesAndEventPeriodTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesAndEventPeriodTest.kt
index 3b2432c..02b3298 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesAndEventPeriodTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesAndEventPeriodTest.kt
@@ -20,6 +20,7 @@
 import android.view.MotionEvent.ACTION_UP
 import androidx.test.filters.SmallTest
 import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.test.util.assertHasValidEventTimes
@@ -92,7 +93,7 @@
     }
 
     @get:Rule
-    val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(
         disableDispatchInRealTime = true,
         eventPeriodOverride = config.eventPeriod
     )
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesTest.kt
index 13379bc..13db791 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendSwipeWithKeyTimesTest.kt
@@ -20,6 +20,7 @@
 import android.view.MotionEvent.ACTION_MOVE
 import android.view.MotionEvent.ACTION_UP
 import androidx.test.filters.SmallTest
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.test.util.assertHasValidEventTimes
@@ -84,9 +85,7 @@
     }
 
     @get:Rule
-    val inputDispatcherRule: TestRule = AndroidInputDispatcher.TestRule(
-        disableDispatchInRealTime = true
-    )
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MotionEventRecorder()
     private val subject = AndroidInputDispatcher(recorder::recordEvent)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendUpTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendUpTest.kt
index 8872c65..67ebc5b 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendUpTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/inputdispatcher/SendUpTest.kt
@@ -22,6 +22,8 @@
 import android.view.MotionEvent.ACTION_UP
 import androidx.test.filters.SmallTest
 import androidx.ui.geometry.Offset
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.android.AndroidInputDispatcher
 import androidx.ui.test.util.MotionEventRecorder
 import androidx.ui.test.util.assertHasValidEventTimes
@@ -50,8 +52,7 @@
     }
 
     @get:Rule
-    val inputDispatcherRule: TestRule =
-        AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MotionEventRecorder()
     private val subject = AndroidInputDispatcher(recorder::recordEvent)
@@ -81,6 +82,26 @@
     }
 
     @Test
+    fun onePointerWithDelay() {
+        subject.sendDownAndCheck(pointer1, position1_1)
+        subject.sendUpAndCheck(pointer1, 2 * eventPeriod)
+        subject.verifyNoGestureInProgress()
+
+        recorder.assertHasValidEventTimes()
+        recorder.events.apply {
+            var t = 0L
+            assertThat(this).hasSize(2)
+
+            this[0].verifyEvent(1, ACTION_DOWN, 0, t) // pointer1
+            this[0].verifyPointer(pointer1, position1_1)
+
+            t += 2 * eventPeriod
+            this[1].verifyEvent(1, ACTION_UP, 0, t) // pointer1
+            this[1].verifyPointer(pointer1, position1_1)
+        }
+    }
+
+    @Test
     fun multiplePointers_ascending() {
         subject.sendDownAndCheck(pointer1, position1_1)
         subject.sendDownAndCheck(pointer2, position2_1)
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendCancelTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendCancelTest.kt
index 939eca6..388bd24 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendCancelTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendCancelTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.MediumTest
 import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.inputdispatcher.verifyNoGestureInProgress
 import androidx.ui.test.partialgesturescope.Common.partialGesture
@@ -50,8 +50,7 @@
     val composeTestRule = createComposeRule()
 
     @get:Rule
-    val inputDispatcherRule: TestRule =
-        AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MultiPointerInputRecorder()
 
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendDownTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendDownTest.kt
index dccbf8f..9e2d7a0 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendDownTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendDownTest.kt
@@ -19,7 +19,7 @@
 import android.os.SystemClock.sleep
 import androidx.test.filters.MediumTest
 import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.partialgesturescope.Common.partialGesture
 import androidx.ui.test.runOnIdleCompose
@@ -49,8 +49,7 @@
     val composeTestRule = createComposeRule()
 
     @get:Rule
-    val inputDispatcherRule: TestRule =
-        AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MultiPointerInputRecorder()
 
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveByTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveByTest.kt
index 22cce9e..8fcfe89 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveByTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveByTest.kt
@@ -19,7 +19,7 @@
 import android.os.SystemClock.sleep
 import androidx.test.filters.MediumTest
 import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.movePointerBy
 import androidx.ui.test.partialgesturescope.Common.partialGesture
@@ -57,8 +57,7 @@
     val composeTestRule = createComposeRule()
 
     @get:Rule
-    val inputDispatcherRule: TestRule =
-        AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MultiPointerInputRecorder()
 
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveTest.kt
index 35c49a0..bd7e890 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.MediumTest
 import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.partialgesturescope.Common.partialGesture
 import androidx.ui.test.sendCancel
@@ -45,8 +45,7 @@
     val composeTestRule = createComposeRule()
 
     @get:Rule
-    val inputDispatcherRule: TestRule =
-        AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     @Before
     fun setUp() {
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveToTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveToTest.kt
index 5eb9c1f..599bfa3 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveToTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendMoveToTest.kt
@@ -19,7 +19,7 @@
 import android.os.SystemClock.sleep
 import androidx.test.filters.MediumTest
 import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.movePointerTo
 import androidx.ui.test.partialgesturescope.Common.partialGesture
@@ -57,8 +57,7 @@
     val composeTestRule = createComposeRule()
 
     @get:Rule
-    val inputDispatcherRule: TestRule =
-        AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MultiPointerInputRecorder()
 
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendUpTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendUpTest.kt
index 925914d..fc6da04 100644
--- a/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendUpTest.kt
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/partialgesturescope/SendUpTest.kt
@@ -19,7 +19,7 @@
 import android.os.SystemClock.sleep
 import androidx.test.filters.MediumTest
 import androidx.ui.geometry.Offset
-import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.InputDispatcher.InputDispatcherTestRule
 import androidx.ui.test.createComposeRule
 import androidx.ui.test.inputdispatcher.verifyNoGestureInProgress
 import androidx.ui.test.partialgesturescope.Common.partialGesture
@@ -52,8 +52,7 @@
     val composeTestRule = createComposeRule()
 
     @get:Rule
-    val inputDispatcherRule: TestRule =
-        AndroidInputDispatcher.TestRule(disableDispatchInRealTime = true)
+    val inputDispatcherRule: TestRule = InputDispatcherTestRule(disableDispatchInRealTime = true)
 
     private val recorder = MultiPointerInputRecorder()
 
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
index be87be9..4de2e6f 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt
@@ -22,6 +22,7 @@
 import androidx.ui.core.semantics.SemanticsNode
 import androidx.ui.geometry.Offset
 import androidx.ui.geometry.lerp
+import androidx.ui.test.InputDispatcher.Companion.eventPeriod
 import androidx.ui.unit.Duration
 import androidx.ui.unit.IntSize
 import androidx.ui.unit.PxBounds
@@ -29,6 +30,7 @@
 import androidx.ui.unit.milliseconds
 import androidx.ui.util.lerp
 import kotlin.math.atan2
+import kotlin.math.ceil
 import kotlin.math.cos
 import kotlin.math.roundToInt
 import kotlin.math.sign
@@ -67,7 +69,7 @@
         }
 
     internal fun dispose() {
-        InputDispatcher.saveState(owner, inputDispatcher)
+        inputDispatcher.saveState(owner)
         _semanticsNode = null
         _inputDispatcher = null
     }
@@ -358,10 +360,13 @@
     require(endVelocity >= 0f) {
         "Velocity cannot be $endVelocity, it must be positive"
     }
-    // TODO(b/146551983): require that duration >= 2.5 * eventPeriod
-    // TODO(b/146551983): check that eventPeriod < 40 milliseconds
-    require(duration >= 25.milliseconds) {
-        "Duration must be at least 25ms because velocity requires at least 3 input events"
+    require(eventPeriod < 40.milliseconds.inMilliseconds()) {
+        "InputDispatcher.eventPeriod must be smaller than 40ms in order to generate velocities"
+    }
+    val minimumDuration = ceil(2.5f * eventPeriod).roundToInt()
+    require(duration >= minimumDuration.milliseconds) {
+        "Duration must be at least ${minimumDuration}ms because " +
+                "velocity requires at least 3 input events"
     }
     val globalStart = localToGlobal(start)
     val globalEnd = localToGlobal(end)
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
index 5d1d32c..e66e165 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt
@@ -25,7 +25,13 @@
 import androidx.ui.test.android.AndroidOwnerRegistry
 import androidx.ui.unit.Duration
 import androidx.ui.unit.inMilliseconds
+import androidx.ui.unit.milliseconds
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
 import java.util.WeakHashMap
+import kotlin.math.max
+import kotlin.math.roundToInt
 
 /**
  * Interface for dispatching full and partial gestures.
@@ -45,8 +51,33 @@
  * Chaining methods:
  * * [delay]
  */
-internal interface InputDispatcher {
+internal abstract class InputDispatcher {
     companion object : AndroidOwnerRegistry.OnRegistrationChangedListener {
+        /**
+         * Whether or not events with an eventTime in the future should be dispatched at that
+         * exact eventTime. If `true`, will sleep until the eventTime, if `false`, will send the
+         * event immediately without blocking.
+         */
+        private var dispatchInRealTime: Boolean = true
+
+        /**
+         * The minimum time between two successive injected MotionEvents, 10 milliseconds.
+         * 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).
+         */
+        var eventPeriod = 10.milliseconds.inMilliseconds()
+            private set
+
+        /**
+         * Indicates that [nextDownTime] is not set
+         */
+        private const val DownTimeNotSet = -1L
+
+        /**
+         * Stores the [InputDispatcherState] of each [Owner]. The state will be restored in an
+         * [InputDispatcher] when it is created for an owner that has a state stored.
+         */
         private val states = WeakHashMap<Owner, InputDispatcherState>()
 
         init {
@@ -61,18 +92,15 @@
             val view = owner.view
             return AndroidInputDispatcher { view.dispatchTouchEvent(it) }.apply {
                 states.remove(owner)?.also {
-                    restoreState(it)
+                    // TODO(b/157653315): Move restore state to constructor
+                    if (it.partialGesture != null) {
+                        nextDownTime = it.nextDownTime
+                        partialGesture = it.partialGesture
+                    }
                 }
             }
         }
 
-        internal fun saveState(owner: Owner?, inputDispatcher: InputDispatcher) {
-            // Owner may have been removed already
-            if (owner != null && AndroidOwnerRegistry.getUnfilteredOwners().contains(owner)) {
-                states[owner] = inputDispatcher.getState()
-            }
-        }
-
         override fun onRegistrationChanged(owner: AndroidOwner, registered: Boolean) {
             if (!registered) {
                 states.remove(owner)
@@ -80,6 +108,96 @@
         }
     }
 
+    internal fun saveState(owner: Owner?) {
+        if (owner != null && AndroidOwnerRegistry.getUnfilteredOwners().contains(owner)) {
+            states[owner] = InputDispatcherState(nextDownTime, partialGesture)
+        }
+    }
+
+    protected var nextDownTime = DownTimeNotSet
+    protected var partialGesture: PartialGesture? = null
+
+    /**
+     * Indicates if a gesture is in progress or not. A gesture is in progress if at least one
+     * finger is (still) touching the screen.
+     */
+    val isGestureInProgress: Boolean
+        get() = partialGesture != null
+
+    /**
+     * The current time, in the time scale used by gesture events.
+     */
+    protected abstract val now: Long
+
+    /**
+     * Generates the downTime of the next gesture with the given [duration]. The gesture's
+     * [duration] is necessary to facilitate chaining of gestures: if another gesture is made
+     * after the next one, it will start exactly [duration] after the start of the next gesture.
+     * Always use this method to determine the downTime of the [down event][sendDown] of a gesture.
+     *
+     * If the duration is unknown when calling this method, use a duration of zero and update
+     * with [moveNextDownTime] when the duration is known, or use [moveNextDownTime]
+     * incrementally if the gesture unfolds gradually.
+     */
+    private fun generateDownTime(duration: Duration): Long {
+        val downTime = if (nextDownTime == DownTimeNotSet) {
+            now
+        } else {
+            nextDownTime
+        }
+        nextDownTime = downTime + duration.inMilliseconds()
+        return downTime
+    }
+
+    /**
+     * Moves the start time of the next gesture ahead by the given [duration]. Does not affect
+     * any event time from the current gesture. Use this when the expected duration passed to
+     * [generateDownTime] has changed.
+     */
+    private fun moveNextDownTime(duration: Duration) {
+        generateDownTime(duration)
+    }
+
+    /**
+     * Increases the eventTime with the given [time]. Also pushes the downTime for the next
+     * chained gesture by the same amount to facilitate chaining.
+     */
+    private fun PartialGesture.increaseEventTime(time: Long = eventPeriod) {
+        moveNextDownTime(time.milliseconds)
+        lastEventTime += time
+    }
+
+    /**
+     * Delays the next gesture by the given [duration], but does not block. Guarantees that the
+     * first event time of the next gesture will be exactly [duration] later then if that gesture
+     * would be injected without this delay, provided that the next gesture is started using the
+     * same [InputDispatcher] instance as the one used to end the last gesture.
+     *
+     * Note: this does not affect the time of the next event for the _current_ partial gesture,
+     * using [sendMove], [sendUp] and [sendCancel], but it will affect the time of the _next_
+     * gesture (including partial gestures started with [sendDown]).
+     *
+     * @param duration The duration of the delay. Must be positive
+     */
+    fun delay(duration: Duration) {
+        require(duration >= Duration.Zero) {
+            "duration of a delay can only be positive, not $duration"
+        }
+        moveNextDownTime(duration)
+    }
+
+    /**
+     * Blocks until uptime of [time], if [dispatchInRealTime] is `true`.
+     */
+    protected fun sleepUntil(time: Long) {
+        if (dispatchInRealTime) {
+            val currTime = now
+            if (currTime < time) {
+                Thread.sleep(time - currTime)
+            }
+        }
+    }
+
     /**
      * Sends a click event at [position]. There will be 10ms in between the down and the up
      * event. This method blocks until all input events have been dispatched.
@@ -143,21 +261,79 @@
         curves: List<(Long) -> Offset>,
         duration: Duration,
         keyTimes: List<Long> = emptyList()
-    )
+    ) {
+        val startTime = 0L
+        val endTime = duration.inMilliseconds()
+
+        // Validate input
+        require(duration >= 1.milliseconds) {
+            "duration must be at least 1 millisecond, not $duration"
+        }
+        val validRange = startTime..endTime
+        require(keyTimes.all { it in validRange }) {
+            "keyTimes contains timestamps out of range [$startTime..$endTime]: $keyTimes"
+        }
+        require(keyTimes.asSequence().zipWithNext { a, b -> a <= b }.all { it }) {
+            "keyTimes must be sorted: $keyTimes"
+        }
+
+        // Send down events
+        curves.forEachIndexed { i, curve ->
+            sendDown(i, curve(startTime))
+        }
+
+        // Send move events between each consecutive pair in [t0, ..keyTimes, tN]
+        var currTime = startTime
+        var key = 0
+        while (currTime < endTime) {
+            // advance key
+            while (key < keyTimes.size && keyTimes[key] <= currTime) {
+                key++
+            }
+            // send events between t and next keyTime
+            val tNext = if (key < keyTimes.size) keyTimes[key] else endTime
+            sendPartialSwipes(curves, currTime, tNext)
+            currTime = tNext
+        }
+
+        // And end with up events
+        repeat(curves.size) {
+            sendUp(it)
+        }
+    }
 
     /**
-     * Blocks for the given [duration] in order to delay the next gesture. Guarantees that the
-     * first event time of the next gesture will be exactly [duration] later then if that gesture
-     * would be injected without this delay, provided that the next gesture is started using the
-     * same [InputDispatcher] instance as the one used to end the last gesture.
+     * Sends move events between `f([t0])` and `f([tN])` during the time window `(downTime + t0,
+     * downTime + tN]`, using [fs] to sample the coordinate of each event. The number of events
+     * sent (#numEvents) is such that the time between each event is as close to [eventPeriod] as
+     * possible, but at least 1. The first event is sent at time `downTime + (tN - t0) /
+     * #numEvents`, the last event is sent at time tN.
      *
-     * Note: this does not affect the time of the next event for the _current_ partial gesture,
-     * using [sendMove], [sendUp] and [sendCancel], but it will affect the time of the _next_
-     * gesture (including partial gestures started with [sendDown]).
-     *
-     * @param duration The duration of the delay. Must be positive
+     * @param fs The functions that define the coordinates of the respective gestures over time
+     * @param t0 The start time of this segment of the swipe, in milliseconds relative to downTime
+     * @param tN The end time of this segment of the swipe, in milliseconds relative to downTime
      */
-    fun delay(duration: Duration)
+    private fun sendPartialSwipes(
+        fs: List<(Long) -> Offset>,
+        t0: Long,
+        tN: Long
+    ) {
+        var step = 0
+        // How many steps will we take between t0 and tN? At least 1, and a number that will
+        // bring as as close to eventPeriod as possible
+        val steps = max(1, ((tN - t0) / eventPeriod.toFloat()).roundToInt())
+
+        var tPrev = t0
+        while (step++ < steps) {
+            val progress = step / steps.toFloat()
+            val t = androidx.ui.util.lerp(t0, tN, progress)
+            fs.forEachIndexed { i, f ->
+                movePointer(i, f(t))
+            }
+            sendMove(t - tPrev)
+            tPrev = t
+        }
+    }
 
     /**
      * During a partial gesture, returns the position of the last touch event of the given
@@ -167,7 +343,9 @@
      * @return The current position of the pointer with the given [pointerId], or `null` if the
      * pointer is not currently in use
      */
-    fun getCurrentPosition(pointerId: Int): Offset?
+    fun getCurrentPosition(pointerId: Int): Offset? {
+        return partialGesture?.lastPositions?.get(pointerId)
+    }
 
     /**
      * Sends a down event at [position] for the pointer with the given [pointerId], starting a
@@ -202,7 +380,27 @@
      * @see sendUp
      * @see sendCancel
      */
-    fun sendDown(pointerId: Int, position: Offset)
+    fun sendDown(pointerId: Int, position: Offset) {
+        var gesture = partialGesture
+
+        // Check if this pointer is not already down
+        require(gesture == null || !gesture.lastPositions.containsKey(pointerId)) {
+            "Cannot send DOWN event, a gesture is already in progress for pointer $pointerId"
+        }
+
+        gesture?.flushPointerUpdates()
+
+        // Start a new gesture, or add the pointerId to the existing gesture
+        if (gesture == null) {
+            gesture = PartialGesture(generateDownTime(0.milliseconds), position, pointerId)
+            partialGesture = gesture
+        } else {
+            gesture.lastPositions.put(pointerId, position)
+        }
+
+        // Send the DOWN event
+        gesture.sendDown(pointerId)
+    }
 
     /**
      * Updates the position of the pointer with the given [pointerId] to the given [position],
@@ -221,56 +419,176 @@
      * @see sendUp
      * @see sendCancel
      */
-    fun movePointer(pointerId: Int, position: Offset)
+    fun movePointer(pointerId: Int, position: Offset) {
+        val gesture = partialGesture
+
+        // Check if this pointer is in the gesture
+        check(gesture != null) {
+            "Cannot move pointers, no gesture is in progress"
+        }
+        require(gesture.lastPositions.containsKey(pointerId)) {
+            "Cannot move pointer $pointerId, it is not active in the current gesture"
+        }
+
+        gesture.lastPositions.put(pointerId, position)
+        gesture.hasPointerUpdates = true
+    }
 
     /**
-     * Sends a move event 10 milliseconds after the previous injected event of this gesture,
-     * without moving any of the pointers. Use this to commit all changes in pointer location
-     * made with [movePointer]. The sent event will contain the current position of all pointers.
-     * See [sendDown] for more information on how to make complete gestures from partial gestures.
+     * Sends a move event [delay] milliseconds after the previous injected event of this gesture,
+     * without moving any of the pointers. The default [delay] is [10][eventPeriod] milliseconds.
+     * Use this to commit all changes in pointer location made with [movePointer]. The sent event
+     * will contain the current position of all pointers. See [sendDown] for more information on
+     * how to make complete gestures from partial gestures.
+     *
+     * @param delay The time in milliseconds between the previously injected event and the move
+     * event. [10][eventPeriod] milliseconds by default.
      */
-    fun sendMove()
+    fun sendMove(delay: Long = eventPeriod) {
+        val gesture = checkNotNull(partialGesture) {
+            "Cannot send MOVE event, no gesture is in progress"
+        }
+        require(delay >= 0) {
+            "Cannot send MOVE event with a delay of $delay ms"
+        }
+
+        gesture.increaseEventTime(delay)
+        gesture.sendMove()
+        gesture.hasPointerUpdates = false
+    }
 
     /**
-     * Sends an up event for the given [pointerId] at the current position of that pointer, 10
-     * milliseconds after the previous injected event of this gesture. This method blocks until
-     * the input event has been dispatched. See [sendDown] for more information on how to make
-     * complete gestures from partial gestures.
+     * Sends an up event for the given [pointerId] at the current position of that pointer,
+     * [delay] milliseconds after the previous injected event of this gesture. The default
+     * [delay] is 0 milliseconds. This method blocks until the input event has been dispatched.
+     * See [sendDown] for more information on how to make complete gestures from partial gestures.
      *
      * @param pointerId The id of the pointer to lift up, as supplied in [sendDown]
+     * @param delay The time in milliseconds between the previously injected event and the move
+     * event. 0 milliseconds by default.
      *
      * @see sendDown
      * @see movePointer
      * @see sendMove
      * @see sendCancel
      */
-    fun sendUp(pointerId: Int)
+    fun sendUp(pointerId: Int, delay: Long = 0) {
+        val gesture = partialGesture
+
+        // Check if this pointer is in the gesture
+        check(gesture != null) {
+            "Cannot send UP event, no gesture is in progress"
+        }
+        require(gesture.lastPositions.containsKey(pointerId)) {
+            "Cannot send UP event for pointer $pointerId, it is not active in the current gesture"
+        }
+        require(delay >= 0) {
+            "Cannot send UP event with a delay of $delay ms"
+        }
+
+        gesture.flushPointerUpdates()
+        gesture.increaseEventTime(delay)
+
+        // First send the UP event
+        gesture.sendUp(pointerId)
+
+        // Then remove the pointer, and end the gesture if no pointers are left
+        gesture.lastPositions.remove(pointerId)
+        if (gesture.lastPositions.isEmpty) {
+            partialGesture = null
+        }
+    }
 
     /**
-     * Sends a cancel event 10 milliseconds after the previous injected event of this gesture.
-     * This method blocks until the input event has been dispatched. See [sendDown] for more
-     * information on how to make complete gestures from partial gestures.
+     * Sends a cancel event [delay] milliseconds after the previous injected event of this
+     * gesture. The default [delay] is [10][eventPeriod] milliseconds. This method blocks until
+     * the input event has been dispatched. See [sendDown] for more information on how to make
+     * complete gestures from partial gestures.
+     *
+     * @param delay The time in milliseconds between the previously injected event and the cancel
+     * event. [10][eventPeriod] milliseconds by default.
      *
      * @see sendDown
      * @see movePointer
      * @see sendMove
      * @see sendUp
      */
-    fun sendCancel()
+    fun sendCancel(delay: Long = eventPeriod) {
+        val gesture = checkNotNull(partialGesture) {
+            "Cannot send CANCEL event, no gesture is in progress"
+        }
+        require(delay >= 0) {
+            "Cannot send CANCEL event with a delay of $delay ms"
+        }
+
+        gesture.increaseEventTime(delay)
+        gesture.sendCancel()
+        partialGesture = null
+    }
 
     /**
-     * Returns the state of this input dispatcher, in case a partial gesture is in progress.
+     * Sends a MOVE event with all pointer locations, if any of the pointers has been moved by
+     * [movePointer] since the last MOVE event.
      */
-    fun getState(): InputDispatcherState
+    private fun PartialGesture.flushPointerUpdates() {
+        if (hasPointerUpdates) {
+            sendMove(eventPeriod)
+        }
+    }
+
+    protected abstract fun PartialGesture.sendDown(pointerId: Int)
+
+    protected abstract fun PartialGesture.sendMove()
+
+    protected abstract fun PartialGesture.sendUp(pointerId: Int)
+
+    protected abstract fun PartialGesture.sendCancel()
 
     /**
-     * Restores the state of this input dispatcher, in case a partial gesture was in progress. If
-     * a partial gesture was not in progress, no state is restored.
+     * A test rule that modifies [InputDispatcher]s behavior. Can be used to disable
+     * dispatching of MotionEvents in real time (skips the sleep before injection of an event) or
+     * to change the time between consecutive injected events.
      *
-     * @param state The state to restore
+     * @param disableDispatchInRealTime If set, controls whether or not events with an eventTime
+     * in the future will be dispatched as soon as possible or at that exact eventTime. If
+     * `false` or not set, will sleep until the eventTime, if `true`, will send the event
+     * immediately without blocking. See also [dispatchInRealTime].
+     * @param eventPeriodOverride If set, specifies a different period in milliseconds between
+     * two consecutive injected motion events injected by this [InputDispatcher]. If not
+     * set, the event period of 10 milliseconds is unchanged.
+     *
+     * @see InputDispatcher.eventPeriod
      */
-    fun restoreState(state: InputDispatcherState)
-    // TODO(b/157653315): Move restore state to constructor
+    internal class InputDispatcherTestRule(
+        private val disableDispatchInRealTime: Boolean = false,
+        private val eventPeriodOverride: Long? = null
+    ) : TestRule {
+
+        override fun apply(base: Statement, description: Description?): Statement {
+            return ModifyingStatement(base)
+        }
+
+        inner class ModifyingStatement(private val base: Statement) : Statement() {
+            override fun evaluate() {
+                if (disableDispatchInRealTime) {
+                    dispatchInRealTime = false
+                }
+                if (eventPeriodOverride != null) {
+                    InputDispatcher.eventPeriod = eventPeriodOverride
+                }
+                try {
+                    base.evaluate()
+                } finally {
+                    if (disableDispatchInRealTime) {
+                        dispatchInRealTime = true
+                    }
+                    if (eventPeriodOverride != null) {
+                        InputDispatcher.eventPeriod = 10L
+                    }
+                }
+            }
+        }
+    }
 }
 
 /**
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
index 556b1ac..5f01b4b 100644
--- 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
@@ -29,215 +29,38 @@
 import android.view.MotionEvent.ACTION_UP
 import androidx.ui.geometry.Offset
 import androidx.ui.test.InputDispatcher
-import androidx.ui.test.InputDispatcherState
 import androidx.ui.test.PartialGesture
-import androidx.ui.unit.Duration
-import androidx.ui.unit.inMilliseconds
-import androidx.ui.unit.milliseconds
-import androidx.ui.util.lerp
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
-import kotlin.math.max
-import kotlin.math.roundToInt
 
 internal class AndroidInputDispatcher(
     private val sendEvent: (MotionEvent) -> Unit
-) : InputDispatcher {
-    companion object {
-        /**
-         * Whether or not events with an eventTime in the future should be dispatched at that
-         * exact eventTime. If `true`, will sleep until the eventTime, if `false`, will send the
-         * event immediately without blocking.
-         */
-        private var dispatchInRealTime: Boolean = true
-
-        /**
-         * The minimum time between two successive injected MotionEvents, 10 milliseconds.
-         * 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 var eventPeriod = 10.milliseconds.inMilliseconds()
-
-        /**
-         * Indicates that [nextDownTime] is not set
-         */
-        private const val DownTimeNotSet = -1L
-    }
+) : InputDispatcher() {
 
     private val handler = Handler(Looper.getMainLooper())
-    private var nextDownTime = DownTimeNotSet
-    private var partialGesture: PartialGesture? = null
 
-    override fun getState(): InputDispatcherState {
-        return InputDispatcherState(nextDownTime, partialGesture)
-    }
+    override val now: Long get() = SystemClock.uptimeMillis()
 
-    override fun restoreState(state: InputDispatcherState) {
-        if (state.partialGesture != null) {
-            nextDownTime = state.nextDownTime
-            partialGesture = state.partialGesture
-        }
-    }
-
-    /**
-     * Generates the downTime of the next gesture with the given [duration]. The gesture's
-     * [duration] is necessary to facilitate chaining of gestures: if another gesture is made
-     * after the next one, it will start exactly [duration] after the start of the next gesture.
-     * Always use this method to determine the downTime of the [ACTION_DOWN] event of a gesture.
-     *
-     * If the duration is unknown when calling this method, use a duration of zero and update
-     * with [moveNextDownTime] when the duration is known, or use [moveNextDownTime]
-     * incrementally if the gesture unfolds gradually.
-     */
-    private fun generateDownTime(duration: Duration): Long {
-        val downTime = if (nextDownTime == DownTimeNotSet) {
-            SystemClock.uptimeMillis()
-        } else {
-            nextDownTime
-        }
-        nextDownTime = downTime + duration.inMilliseconds()
-        return downTime
-    }
-
-    /**
-     * Moves the start time of the next gesture ahead by the given [duration]. Does not affect
-     * any event time from the current gesture. Use this when the expected duration passed to
-     * [generateDownTime] has changed.
-     */
-    private fun moveNextDownTime(duration: Duration) {
-        generateDownTime(duration)
-    }
-
-    override fun delay(duration: Duration) {
-        require(duration >= Duration.Zero) {
-            "duration of a delay can only be positive, not $duration"
-        }
-        moveNextDownTime(duration)
-        sleepUntil(nextDownTime)
-    }
-
-    override fun getCurrentPosition(pointerId: Int): Offset? {
-        return partialGesture?.lastPositions?.get(pointerId)
-    }
-
-    override fun sendDown(pointerId: Int, position: Offset) {
-        var gesture = partialGesture
-
-        // Check if this pointer is not already down
-        require(gesture == null || !gesture.lastPositions.containsKey(pointerId)) {
-            "Cannot send DOWN event, a gesture is already in progress for pointer $pointerId"
-        }
-
-        gesture?.flushPointerUpdates()
-
-        // Start a new gesture, or add the pointerId to the existing gesture
-        if (gesture == null) {
-            gesture = PartialGesture(generateDownTime(0.milliseconds), position, pointerId)
-            partialGesture = gesture
-        } else {
-            gesture.lastPositions.put(pointerId, position)
-        }
-
-        // Send the ACTION_DOWN or ACTION_POINTER_DOWN
-        val positions = gesture.lastPositions
-        gesture.sendMotionEvent(
-            if (positions.size() == 1) ACTION_DOWN else ACTION_POINTER_DOWN,
-            positions.indexOfKey(pointerId)
+    override fun PartialGesture.sendDown(pointerId: Int) {
+        sendMotionEvent(
+            if (lastPositions.size() == 1) ACTION_DOWN else ACTION_POINTER_DOWN,
+            lastPositions.indexOfKey(pointerId)
         )
     }
 
-    // Move 1 pointer and don't send a move event
-    override fun movePointer(pointerId: Int, position: Offset) {
-        val gesture = partialGesture
-
-        // Check if this pointer is in the gesture
-        check(gesture != null) {
-            "Cannot move pointers, no gesture is in progress"
-        }
-        require(gesture.lastPositions.containsKey(pointerId)) {
-            "Cannot move pointer $pointerId, it is not active in the current gesture"
-        }
-
-        gesture.lastPositions.put(pointerId, position)
-        gesture.hasPointerUpdates = true
+    override fun PartialGesture.sendMove() {
+        sendMotionEvent(ACTION_MOVE, 0)
     }
 
-    // Move 0 pointers and send a move event
-    override fun sendMove() {
-        sendMove(eventPeriod)
-    }
-
-    /**
-     * Sends a move event, [deltaTime] milliseconds after the last event
-     */
-    // TODO(b/157717418): make this public API
-    private fun sendMove(deltaTime: Long) {
-        val gesture = checkNotNull(partialGesture) {
-            "Cannot send MOVE event, no gesture is in progress"
-        }
-
-        gesture.increaseEventTime(deltaTime)
-        gesture.sendMotionEvent(ACTION_MOVE, 0)
-        gesture.hasPointerUpdates = false
-    }
-
-    override fun sendUp(pointerId: Int) {
-        val gesture = partialGesture
-
-        // Check if this pointer is in the gesture
-        check(gesture != null) {
-            "Cannot send UP event, no gesture is in progress"
-        }
-        require(gesture.lastPositions.containsKey(pointerId)) {
-            "Cannot send UP event for pointer $pointerId, it is not active in the current gesture"
-        }
-
-        gesture.flushPointerUpdates()
-
-        // First send the ACTION_UP or ACTION_POINTER_UP
-        val positions = gesture.lastPositions
-        gesture.sendMotionEvent(
-            if (positions.size() == 1) ACTION_UP else ACTION_POINTER_UP,
-            positions.indexOfKey(pointerId)
+    override fun PartialGesture.sendUp(pointerId: Int) {
+        sendMotionEvent(
+            if (lastPositions.size() == 1) ACTION_UP else ACTION_POINTER_UP,
+            lastPositions.indexOfKey(pointerId)
         )
-
-        // Then remove the pointer, and end the gesture if no pointers are left
-        positions.remove(pointerId)
-        if (positions.isEmpty) {
-            partialGesture = null
-        }
     }
 
-    override fun sendCancel() {
-        val gesture = checkNotNull(partialGesture) {
-            "Cannot send CANCEL event, no gesture is in progress"
-        }
-
-        gesture.increaseEventTime()
-        gesture.sendMotionEvent(ACTION_CANCEL, 0)
-        partialGesture = null
-    }
-
-    /**
-     * Increases the eventTime with the given [time]. Also pushes the downTime for the next
-     * chained gesture by the same amount to facilitate chaining.
-     */
-    private fun PartialGesture.increaseEventTime(time: Long = eventPeriod) {
-        moveNextDownTime(time.milliseconds)
-        lastEventTime += time
-    }
-
-    /**
-     * Sends a MOVE event with all pointer locations, if any of the pointers has been moved by
-     * [movePointer] since the last MOVE event.
-     */
-    private fun PartialGesture.flushPointerUpdates() {
-        if (hasPointerUpdates) {
-            sendMove()
-        }
+    override fun PartialGesture.sendCancel() {
+        sendMotionEvent(ACTION_CANCEL, 0)
     }
 
     /**
@@ -258,86 +81,8 @@
         )
     }
 
-    override fun sendSwipes(
-        curves: List<(Long) -> Offset>,
-        duration: Duration,
-        keyTimes: List<Long>
-    ) {
-        val startTime = 0L
-        val endTime = duration.inMilliseconds()
-
-        // Validate input
-        require(duration >= 1.milliseconds) {
-            "duration must be at least 1 millisecond, not $duration"
-        }
-        val validRange = startTime..endTime
-        require(keyTimes.all { it in validRange }) {
-            "keyTimes contains timestamps out of range [$startTime..$endTime]: $keyTimes"
-        }
-        require(keyTimes.asSequence().zipWithNext { a, b -> a <= b }.all { it }) {
-            "keyTimes must be sorted: $keyTimes"
-        }
-
-        // Send down events
-        curves.forEachIndexed { i, curve ->
-            sendDown(i, curve(startTime))
-        }
-
-        // Send move events between each consecutive pair in [t0, ..keyTimes, tN]
-        var currTime = startTime
-        var key = 0
-        while (currTime < endTime) {
-            // advance key
-            while (key < keyTimes.size && keyTimes[key] <= currTime) {
-                key++
-            }
-            // send events between t and next keyTime
-            val tNext = if (key < keyTimes.size) keyTimes[key] else endTime
-            sendPartialSwipes(curves, currTime, tNext)
-            currTime = tNext
-        }
-
-        // And end with up events
-        repeat(curves.size) {
-            sendUp(it)
-        }
-    }
-
     /**
-     * Sends move events between `f([t0])` and `f([tN])` during the time window `(downTime + t0,
-     * downTime + tN]`, using [fs] to sample the coordinate of each event. The number of events
-     * sent (#numEvents) is such that the time between each event is as close to [eventPeriod] as
-     * possible, but at least 1. The first event is sent at time `downTime + (tN - t0) /
-     * #numEvents`, the last event is sent at time tN.
-     *
-     * @param fs The functions that define the coordinates of the respective gestures over time
-     * @param t0 The start time of this segment of the swipe, in milliseconds relative to downTime
-     * @param tN The end time of this segment of the swipe, in milliseconds relative to downTime
-     */
-    private fun sendPartialSwipes(
-        fs: List<(Long) -> Offset>,
-        t0: Long,
-        tN: Long
-    ) {
-        var step = 0
-        // How many steps will we take between t0 and tN? At least 1, and a number that will
-        // bring as as close to eventPeriod as possible
-        val steps = max(1, ((tN - t0) / eventPeriod.toFloat()).roundToInt())
-
-        var tPrev = t0
-        while (step++ < steps) {
-            val progress = step / steps.toFloat()
-            val t = lerp(t0, tN, progress)
-            fs.forEachIndexed { i, f ->
-                movePointer(i, f(t))
-            }
-            sendMove(t - tPrev)
-            tPrev = t
-        }
-    }
-
-    /**
-     * Sends an event with the given parameters. Method blocks if [dispatchInRealTime] is `true`.
+     * Sends an event with the given parameters.
      */
     private fun sendMotionEvent(
         downTime: Long,
@@ -375,15 +120,6 @@
         )
     }
 
-    private fun sleepUntil(time: Long) {
-        if (dispatchInRealTime) {
-            val currTime = SystemClock.uptimeMillis()
-            if (currTime < time) {
-                SystemClock.sleep(time - currTime)
-            }
-        }
-    }
-
     /**
      * Sends the [event] to the MotionEvent dispatcher and [recycles][MotionEvent.recycle] it
      * regardless of the result. This method blocks until the event is sent.
@@ -404,52 +140,4 @@
             latch.await()
         }
     }
-
-    /**
-     * A test rule that modifies [AndroidInputDispatcher]s behavior. Can be used to disable
-     * dispatching of MotionEvents in real time (skips the sleep before injection of an event) or
-     * to change the time between consecutive injected events.
-     *
-     * @param disableDispatchInRealTime If set, controls whether or not events with an eventTime
-     * in the future will be dispatched as soon as possible or at that exact eventTime. If
-     * `false` or not set, will sleep until the eventTime, if `true`, will send the event
-     * immediately without blocking. See also [dispatchInRealTime].
-     * @param eventPeriodOverride If set, specifies a different period in milliseconds between
-     * two consecutive injected motion events injected by this [AndroidInputDispatcher]. If not
-     * set, the event period of 10 milliseconds is unchanged.
-     *
-     * @see AndroidInputDispatcher.eventPeriod
-     */
-    internal class TestRule(
-        private val disableDispatchInRealTime: Boolean = false,
-        private val eventPeriodOverride: Long? = null
-    ) : org.junit.rules.TestRule {
-
-        val eventPeriod get() = AndroidInputDispatcher.eventPeriod
-
-        override fun apply(base: Statement, description: Description?): Statement {
-            return ModifyingStatement(base)
-        }
-
-        inner class ModifyingStatement(private val base: Statement) : Statement() {
-            override fun evaluate() {
-                if (disableDispatchInRealTime) {
-                    dispatchInRealTime = false
-                }
-                if (eventPeriodOverride != null) {
-                    AndroidInputDispatcher.eventPeriod = eventPeriodOverride
-                }
-                try {
-                    base.evaluate()
-                } finally {
-                    if (disableDispatchInRealTime) {
-                        dispatchInRealTime = true
-                    }
-                    if (eventPeriodOverride != null) {
-                        AndroidInputDispatcher.eventPeriod = 10L
-                    }
-                }
-            }
-        }
-    }
 }
diff --git a/ui/ui-text-core/api/0.1.0-dev14.txt b/ui/ui-text-core/api/0.1.0-dev14.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/0.1.0-dev14.txt
+++ b/ui/ui-text-core/api/0.1.0-dev14.txt
@@ -653,6 +653,7 @@
   }
 
   public final class TextRangeKt {
+    method public static androidx.ui.text.TextRange TextRange(int index);
     method public static String substring(CharSequence, androidx.ui.text.TextRange range);
   }
 
diff --git a/ui/ui-text-core/api/current.txt b/ui/ui-text-core/api/current.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/current.txt
+++ b/ui/ui-text-core/api/current.txt
@@ -653,6 +653,7 @@
   }
 
   public final class TextRangeKt {
+    method public static androidx.ui.text.TextRange TextRange(int index);
     method public static String substring(CharSequence, androidx.ui.text.TextRange range);
   }
 
diff --git a/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt b/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt
+++ b/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt
@@ -653,6 +653,7 @@
   }
 
   public final class TextRangeKt {
+    method public static androidx.ui.text.TextRange TextRange(int index);
     method public static String substring(CharSequence, androidx.ui.text.TextRange range);
   }
 
diff --git a/ui/ui-text-core/api/public_plus_experimental_current.txt b/ui/ui-text-core/api/public_plus_experimental_current.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-text-core/api/public_plus_experimental_current.txt
@@ -653,6 +653,7 @@
   }
 
   public final class TextRangeKt {
+    method public static androidx.ui.text.TextRange TextRange(int index);
     method public static String substring(CharSequence, androidx.ui.text.TextRange range);
   }
 
diff --git a/ui/ui-text-core/api/restricted_0.1.0-dev14.txt b/ui/ui-text-core/api/restricted_0.1.0-dev14.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/restricted_0.1.0-dev14.txt
+++ b/ui/ui-text-core/api/restricted_0.1.0-dev14.txt
@@ -653,6 +653,7 @@
   }
 
   public final class TextRangeKt {
+    method public static androidx.ui.text.TextRange TextRange(int index);
     method public static String substring(CharSequence, androidx.ui.text.TextRange range);
   }
 
diff --git a/ui/ui-text-core/api/restricted_current.txt b/ui/ui-text-core/api/restricted_current.txt
index eadecd6c..06f0066 100644
--- a/ui/ui-text-core/api/restricted_current.txt
+++ b/ui/ui-text-core/api/restricted_current.txt
@@ -653,6 +653,7 @@
   }
 
   public final class TextRangeKt {
+    method public static androidx.ui.text.TextRange TextRange(int index);
     method public static String substring(CharSequence, androidx.ui.text.TextRange range);
   }
 
diff --git a/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/BackspaceKeyEditOpTest.kt b/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/BackspaceKeyEditOpTest.kt
index 6558120..22c0541 100644
--- a/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/BackspaceKeyEditOpTest.kt
+++ b/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/BackspaceKeyEditOpTest.kt
@@ -40,7 +40,7 @@
 
     @Test
     fun test_delete() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         BackspaceKeyEditOp().process(eb)
 
@@ -51,7 +51,7 @@
 
     @Test
     fun test_delete_from_offset0() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         BackspaceKeyEditOp().process(eb)
 
@@ -73,7 +73,7 @@
 
     @Test
     fun test_delete_with_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
         eb.setComposition(2, 3)
 
         BackspaceKeyEditOp().process(eb)
@@ -85,7 +85,7 @@
 
     @Test
     fun test_delete_surrogate_pair() {
-        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2, 2))
+        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2))
 
         BackspaceKeyEditOp().process(eb)
 
@@ -107,7 +107,7 @@
 
     @Test
     fun test_delete_with_composition_surrogate_pair() {
-        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2, 2))
+        val eb = EditingBuffer("$SP1$SP2$SP3$SP4$SP5", TextRange(2))
         eb.setComposition(4, 6)
 
         BackspaceKeyEditOp().process(eb)
@@ -122,7 +122,7 @@
     fun test_delete_with_composition_zwj_emoji() {
         val eb = EditingBuffer(
             "$ZWJ_EMOJI$ZWJ_EMOJI",
-            TextRange(ZWJ_EMOJI.length, ZWJ_EMOJI.length))
+            TextRange(ZWJ_EMOJI.length))
 
         BackspaceKeyEditOp().process(eb)
 
diff --git a/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/MoveCursorEditOpTest.kt b/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/MoveCursorEditOpTest.kt
index 5918079..4e38697 100644
--- a/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/MoveCursorEditOpTest.kt
+++ b/ui/ui-text-core/src/androidAndroidTest/kotlin/androidx/ui/input/MoveCursorEditOpTest.kt
@@ -39,7 +39,7 @@
 
     @Test
     fun test_left() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         MoveCursorEditOp(-1).process(eb)
 
@@ -50,7 +50,7 @@
 
     @Test
     fun test_left_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         MoveCursorEditOp(-2).process(eb)
 
@@ -61,7 +61,7 @@
 
     @Test
     fun test_left_from_offset0() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         MoveCursorEditOp(-1).process(eb)
 
@@ -72,7 +72,7 @@
 
     @Test
     fun test_right() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         MoveCursorEditOp(1).process(eb)
 
@@ -83,7 +83,7 @@
 
     @Test
     fun test_right_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         MoveCursorEditOp(2).process(eb)
 
@@ -94,7 +94,7 @@
 
     @Test
     fun test_right_from_offset_length() {
-        val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+        val eb = EditingBuffer("ABCDE", TextRange(5))
 
         MoveCursorEditOp(1).process(eb)
 
@@ -105,7 +105,7 @@
 
     @Test
     fun test_left_surrogate_pair() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         MoveCursorEditOp(-1).process(eb)
 
@@ -116,7 +116,7 @@
 
     @Test
     fun test_left_multiple_surrogate_pair() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         MoveCursorEditOp(-2).process(eb)
 
@@ -127,7 +127,7 @@
 
     @Test
     fun test_right_surrogate_pair() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         MoveCursorEditOp(1).process(eb)
 
@@ -138,7 +138,7 @@
 
     @Test
     fun test_right_multiple_surrogate_pair() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         MoveCursorEditOp(2).process(eb)
 
@@ -150,7 +150,7 @@
     @Test
     @SdkSuppress(minSdkVersion = 26)
     fun test_left_emoji() {
-        val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length, FAMILY.length))
+        val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length))
 
         MoveCursorEditOp(-1).process(eb)
 
@@ -162,7 +162,7 @@
     @Test
     @SdkSuppress(minSdkVersion = 26)
     fun test_right_emoji() {
-        val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length, FAMILY.length))
+        val eb = EditingBuffer("$FAMILY$FAMILY", TextRange(FAMILY.length))
 
         MoveCursorEditOp(1).process(eb)
 
diff --git a/ui/ui-text-core/src/androidMain/kotlin/androidx/ui/text/platform/AndroidParagraphHelper.kt b/ui/ui-text-core/src/androidMain/kotlin/androidx/ui/text/platform/AndroidParagraphHelper.kt
index 5db1374..e9d837f 100644
--- a/ui/ui-text-core/src/androidMain/kotlin/androidx/ui/text/platform/AndroidParagraphHelper.kt
+++ b/ui/ui-text-core/src/androidMain/kotlin/androidx/ui/text/platform/AndroidParagraphHelper.kt
@@ -91,11 +91,15 @@
         typeface = createTypeface(style, typefaceAdapter)
     }
 
-    style.localeList?.let {
+    if (style.localeList != null && style.localeList != LocaleList.current) {
         if (Build.VERSION.SDK_INT >= 24) {
-            textLocales = it.toAndroidLocaleList()
+            textLocales = style.localeList.toAndroidLocaleList()
         } else {
-            val locale = if (it.isEmpty()) Locale.current else it[0]
+            val locale = if (style.localeList.isEmpty()) {
+                Locale.current
+            } else {
+                style.localeList[0]
+            }
             textLocale = locale.toJavaLocale()
         }
     }
diff --git a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/EditProcessor.kt b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/EditProcessor.kt
index 5823dd6..a93b13d 100644
--- a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/EditProcessor.kt
+++ b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/EditProcessor.kt
@@ -35,7 +35,7 @@
 
     // The editing buffer used for applying editor commands from IME.
     private var mBuffer: EditingBuffer =
-        EditingBuffer(initialText = "", initialSelection = TextRange(0, 0))
+        EditingBuffer(initialText = "", initialSelection = TextRange(0))
 
     /**
      * Must be called whenever new editor model arrives.
diff --git a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt
index ec37636..a643659 100644
--- a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt
+++ b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt
@@ -47,7 +47,7 @@
      * A selection range visible to IME.
      * The selection range must be valid range in the given text.
      */
-    val selection: TextRange = TextRange(0, 0),
+    val selection: TextRange = TextRange(0),
 
     /**
      * A composition range visible to IME.
@@ -94,7 +94,7 @@
     @Stable
     val text: String = "",
     @Stable
-    val selection: TextRange = TextRange(0, 0),
+    val selection: TextRange = TextRange(0),
     @Stable
     val composition: TextRange? = null
 ) {
diff --git a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt
index 4fa69a7..98241c88 100644
--- a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt
+++ b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt
@@ -61,3 +61,8 @@
      */
     operator fun contains(offset: Int): Boolean = offset in min until max
 }
+
+/**
+ * Creates a [TextRange] where start is equal to end, and the value of those are [index].
+ */
+fun TextRange(index: Int): TextRange = TextRange(start = index, end = index)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/CommitTextEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/CommitTextEditOpTest.kt
index c75b551..662d88f 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/CommitTextEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/CommitTextEditOpTest.kt
@@ -30,7 +30,7 @@
 
     @Test
     fun test_insert_empty() {
-        val eb = EditingBuffer("", TextRange(0, 0))
+        val eb = EditingBuffer("", TextRange(0))
 
         CommitTextEditOp("X", 1).process(eb)
 
@@ -41,7 +41,7 @@
 
     @Test
     fun test_insert_cursor_tail() {
-        val eb = EditingBuffer("A", TextRange(1, 1))
+        val eb = EditingBuffer("A", TextRange(1))
 
         CommitTextEditOp("X", 1).process(eb)
 
@@ -52,7 +52,7 @@
 
     @Test
     fun test_insert_cursor_head() {
-        val eb = EditingBuffer("A", TextRange(1, 1))
+        val eb = EditingBuffer("A", TextRange(1))
 
         CommitTextEditOp("X", 0).process(eb)
 
@@ -63,7 +63,7 @@
 
     @Test
     fun test_insert_cursor_far_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         CommitTextEditOp("X", 2).process(eb)
 
@@ -74,7 +74,7 @@
 
     @Test
     fun test_insert_cursor_far_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(4, 4))
+        val eb = EditingBuffer("ABCDE", TextRange(4))
 
         CommitTextEditOp("X", -2).process(eb)
 
@@ -85,7 +85,7 @@
 
     @Test
     fun test_insert_empty_text_cursor_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         CommitTextEditOp("", 0).process(eb)
 
@@ -96,7 +96,7 @@
 
     @Test
     fun test_insert_empty_text_cursor_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         CommitTextEditOp("", 1).process(eb)
 
@@ -107,7 +107,7 @@
 
     @Test
     fun test_insert_empty_text_cursor_far_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         CommitTextEditOp("", 2).process(eb)
 
@@ -118,7 +118,7 @@
 
     @Test
     fun test_insert_empty_text_cursor_far_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(4, 4))
+        val eb = EditingBuffer("ABCDE", TextRange(4))
 
         CommitTextEditOp("", -2).process(eb)
 
@@ -129,7 +129,7 @@
 
     @Test
     fun test_cancel_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(1, 4) // Mark "BCD" as composition
         CommitTextEditOp("X", 1).process(eb)
@@ -166,7 +166,7 @@
 
     @Test
     fun test_cursor_position_too_small() {
-        val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+        val eb = EditingBuffer("ABCDE", TextRange(5))
 
         CommitTextEditOp("X", -1000).process(eb)
 
@@ -177,7 +177,7 @@
 
     @Test
     fun test_cursor_position_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+        val eb = EditingBuffer("ABCDE", TextRange(5))
 
         CommitTextEditOp("X", 1000).process(eb)
 
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextEditOpTest.kt
index e6be59e..40840aa 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextEditOpTest.kt
@@ -30,7 +30,7 @@
 
     @Test
     fun test_delete_after() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         DeleteSurroundingTextEditOp(0, 1).process(eb)
 
@@ -41,7 +41,7 @@
 
     @Test
     fun test_delete_before() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         DeleteSurroundingTextEditOp(1, 0).process(eb)
 
@@ -52,7 +52,7 @@
 
     @Test
     fun test_delete_both() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         DeleteSurroundingTextEditOp(1, 1).process(eb)
 
@@ -63,7 +63,7 @@
 
     @Test
     fun test_delete_after_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(2, 2))
+        val eb = EditingBuffer("ABCDE", TextRange(2))
 
         DeleteSurroundingTextEditOp(0, 2).process(eb)
 
@@ -74,7 +74,7 @@
 
     @Test
     fun test_delete_before_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         DeleteSurroundingTextEditOp(2, 0).process(eb)
 
@@ -85,7 +85,7 @@
 
     @Test
     fun test_delete_both_multiple() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         DeleteSurroundingTextEditOp(2, 2).process(eb)
 
@@ -108,7 +108,7 @@
 
     @Test
     fun test_delete_before_too_many() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         DeleteSurroundingTextEditOp(1000, 0).process(eb)
 
@@ -119,7 +119,7 @@
 
     @Test
     fun test_delete_after_too_many() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         DeleteSurroundingTextEditOp(0, 1000).process(eb)
 
@@ -130,7 +130,7 @@
 
     @Test
     fun test_delete_both_too_many() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         DeleteSurroundingTextEditOp(1000, 1000).process(eb)
 
@@ -141,7 +141,7 @@
 
     @Test
     fun test_delete_composition_no_intersection_preceding_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         eb.setComposition(0, 1)
 
@@ -155,7 +155,7 @@
 
     @Test
     fun test_delete_composition_no_intersection_trailing_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         eb.setComposition(4, 5)
 
@@ -169,7 +169,7 @@
 
     @Test
     fun test_delete_composition_intersection_preceding_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         eb.setComposition(0, 3)
 
@@ -183,7 +183,7 @@
 
     @Test
     fun test_delete_composition_intersection_trailing_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         eb.setComposition(3, 5)
 
@@ -197,7 +197,7 @@
 
     @Test
     fun test_delete_covered_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         eb.setComposition(2, 3)
 
@@ -210,7 +210,7 @@
 
     @Test
     fun test_delete_composition_covered() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         eb.setComposition(0, 5)
 
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextInCodePointsEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextInCodePointsEditOpTest.kt
index 908bfb2..1699039a 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextInCodePointsEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/DeleteSurroundingTextInCodePointsEditOpTest.kt
@@ -35,7 +35,7 @@
 
     @Test
     fun test_delete_after() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2, 2))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2))
 
         DeleteSurroundingTextInCodePointsEditOp(0, 1).process(eb)
 
@@ -46,7 +46,7 @@
 
     @Test
     fun test_delete_before() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2, 2))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(2))
 
         DeleteSurroundingTextInCodePointsEditOp(1, 0).process(eb)
 
@@ -57,7 +57,7 @@
 
     @Test
     fun test_delete_both() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         DeleteSurroundingTextInCodePointsEditOp(1, 1).process(eb)
 
@@ -68,7 +68,7 @@
 
     @Test
     fun test_delete_after_multiple() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(4, 4))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(4))
 
         DeleteSurroundingTextInCodePointsEditOp(0, 2).process(eb)
 
@@ -79,7 +79,7 @@
 
     @Test
     fun test_delete_before_multiple() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         DeleteSurroundingTextInCodePointsEditOp(2, 0).process(eb)
 
@@ -90,7 +90,7 @@
 
     @Test
     fun test_delete_both_multiple() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         DeleteSurroundingTextInCodePointsEditOp(2, 2).process(eb)
 
@@ -113,7 +113,7 @@
 
     @Test
     fun test_delete_before_too_many() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         DeleteSurroundingTextInCodePointsEditOp(1000, 0).process(eb)
 
@@ -124,7 +124,7 @@
 
     @Test
     fun test_delete_after_too_many() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         DeleteSurroundingTextInCodePointsEditOp(0, 1000).process(eb)
 
@@ -135,7 +135,7 @@
 
     @Test
     fun test_delete_both_too_many() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         DeleteSurroundingTextInCodePointsEditOp(1000, 1000).process(eb)
 
@@ -146,7 +146,7 @@
 
     @Test
     fun test_delete_composition_no_intersection_preceding_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         eb.setComposition(0, 2)
 
@@ -160,7 +160,7 @@
 
     @Test
     fun test_delete_composition_no_intersection_trailing_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         eb.setComposition(8, 10)
 
@@ -174,7 +174,7 @@
 
     @Test
     fun test_delete_composition_intersection_preceding_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         eb.setComposition(0, 6)
 
@@ -188,7 +188,7 @@
 
     @Test
     fun test_delete_composition_intersection_trailing_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         eb.setComposition(6, 10)
 
@@ -202,7 +202,7 @@
 
     @Test
     fun test_delete_covered_composition() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         eb.setComposition(4, 6)
 
@@ -215,7 +215,7 @@
 
     @Test
     fun test_delete_composition_covered() {
-        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6, 6))
+        val eb = EditingBuffer("$CH1$CH2$CH3$CH4$CH5", TextRange(6))
 
         eb.setComposition(0, 10)
 
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/EditProcessorTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/EditProcessorTest.kt
index b79530c..dc669f0 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/EditProcessorTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/EditProcessorTest.kt
@@ -41,7 +41,7 @@
         val tis: TextInputService = mock()
         val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
 
-        val model = TextFieldValue("ABCDE", TextRange(0, 0))
+        val model = TextFieldValue("ABCDE", TextRange(0))
         proc.onNewState(model, tis, dummyInputSessionToken)
         assertEquals(model, proc.mPreviousState)
         val captor = argumentCaptor<TextFieldValue>()
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/EditingBufferTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/EditingBufferTest.kt
index f4a16ba..80e9955 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/EditingBufferTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/EditingBufferTest.kt
@@ -39,7 +39,7 @@
 
     @Test
     fun test_insert() {
-        val eb = EditingBuffer("", TextRange(0, 0))
+        val eb = EditingBuffer("", TextRange(0))
 
         eb.replace(0, 0, "A")
 
@@ -74,7 +74,7 @@
 
     @Test
     fun test_delete() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.replace(0, 1, "")
 
@@ -149,7 +149,7 @@
 
     @Test
     fun test_setCompostion_and_cancelComposition() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(0, 5) // Make all text as composition
         assertStrWithChars("ABCDE", eb)
@@ -190,7 +190,7 @@
 
     @Test
     fun test_setCompostion_and_commitComposition() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(0, 5) // Make all text as composition
         assertStrWithChars("ABCDE", eb)
@@ -231,7 +231,7 @@
 
     @Test
     fun test_setCursor_and_get_cursor() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.cursor = 1
         assertStrWithChars("ABCDE", eb)
@@ -263,7 +263,7 @@
 
     @Test
     fun test_delete_preceding_cursor_no_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.delete(1, 2)
         assertStrWithChars("ACDE", eb)
@@ -273,7 +273,7 @@
 
     @Test
     fun test_delete_trailing_cursor_no_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(3, 3))
+        val eb = EditingBuffer("ABCDE", TextRange(3))
 
         eb.delete(1, 2)
         assertStrWithChars("ACDE", eb)
@@ -305,7 +305,7 @@
 
     @Test
     fun test_delete_preceding_composition_no_intersection() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(1, 2)
         eb.delete(2, 3)
@@ -318,7 +318,7 @@
 
     @Test
     fun test_delete_trailing_composition_no_intersection() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(3, 4)
         eb.delete(2, 3)
@@ -331,7 +331,7 @@
 
     @Test
     fun test_delete_preceding_composition_intersection() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(1, 3)
         eb.delete(2, 4)
@@ -344,7 +344,7 @@
 
     @Test
     fun test_delete_trailing_composition_intersection() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(3, 5)
         eb.delete(2, 4)
@@ -357,7 +357,7 @@
 
     @Test
     fun test_delete_composition_contains_delrange() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(2, 5)
         eb.delete(3, 4)
@@ -370,7 +370,7 @@
 
     @Test
     fun test_delete_delrange_contains_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(3, 4)
         eb.delete(2, 5)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/FinishComposingTextEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/FinishComposingTextEditOpTest.kt
index 1ef1d40..4e69ff3 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/FinishComposingTextEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/FinishComposingTextEditOpTest.kt
@@ -30,7 +30,7 @@
 
     @Test
     fun test_set() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(1, 4)
         FinishComposingTextEditOp().process(eb)
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingRegionEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingRegionEditOpTest.kt
index ccbc49f..63781b0 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingRegionEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingRegionEditOpTest.kt
@@ -31,7 +31,7 @@
 
     @Test
     fun test_set() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetComposingRegionEditOp(1, 4).process(eb)
 
@@ -44,7 +44,7 @@
 
     @Test
     fun test_preserve_ongoing_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(1, 3)
 
@@ -73,7 +73,7 @@
 
     @Test
     fun test_set_reversed() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetComposingRegionEditOp(4, 1).process(eb)
 
@@ -86,7 +86,7 @@
 
     @Test
     fun test_set_too_small() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetComposingRegionEditOp(-1000, -1000).process(eb)
 
@@ -97,7 +97,7 @@
 
     @Test
     fun test_set_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetComposingRegionEditOp(1000, 1000).process(eb)
 
@@ -108,7 +108,7 @@
 
     @Test
     fun test_set_too_small_and_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetComposingRegionEditOp(-1000, 1000).process(eb)
 
@@ -121,7 +121,7 @@
 
     @Test
     fun test_set_too_small_and_too_large_reversed() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetComposingRegionEditOp(1000, -1000).process(eb)
 
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingTextEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingTextEditOpTest.kt
index d56f3bff..b88cada 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingTextEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/SetComposingTextEditOpTest.kt
@@ -31,7 +31,7 @@
 
     @Test
     fun test_insert_empty() {
-        val eb = EditingBuffer("", TextRange(0, 0))
+        val eb = EditingBuffer("", TextRange(0))
 
         SetComposingTextEditOp("X", 1).process(eb)
 
@@ -44,7 +44,7 @@
 
     @Test
     fun test_insert_cursor_tail() {
-        val eb = EditingBuffer("A", TextRange(1, 1))
+        val eb = EditingBuffer("A", TextRange(1))
 
         SetComposingTextEditOp("X", 1).process(eb)
 
@@ -57,7 +57,7 @@
 
     @Test
     fun test_insert_cursor_head() {
-        val eb = EditingBuffer("A", TextRange(1, 1))
+        val eb = EditingBuffer("A", TextRange(1))
 
         SetComposingTextEditOp("X", 0).process(eb)
 
@@ -70,7 +70,7 @@
 
     @Test
     fun test_insert_cursor_far_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         SetComposingTextEditOp("X", 2).process(eb)
 
@@ -83,7 +83,7 @@
 
     @Test
     fun test_insert_cursor_far_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(4, 4))
+        val eb = EditingBuffer("ABCDE", TextRange(4))
 
         SetComposingTextEditOp("X", -2).process(eb)
 
@@ -96,7 +96,7 @@
 
     @Test
     fun test_insert_empty_text_cursor_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         SetComposingTextEditOp("", 0).process(eb)
 
@@ -107,7 +107,7 @@
 
     @Test
     fun test_insert_empty_text_cursor_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         SetComposingTextEditOp("", 1).process(eb)
 
@@ -118,7 +118,7 @@
 
     @Test
     fun test_insert_empty_text_cursor_far_tail() {
-        val eb = EditingBuffer("ABCDE", TextRange(1, 1))
+        val eb = EditingBuffer("ABCDE", TextRange(1))
 
         SetComposingTextEditOp("", 2).process(eb)
 
@@ -129,7 +129,7 @@
 
     @Test
     fun test_insert_empty_text_cursor_far_head() {
-        val eb = EditingBuffer("ABCDE", TextRange(4, 4))
+        val eb = EditingBuffer("ABCDE", TextRange(4))
 
         SetComposingTextEditOp("", -2).process(eb)
 
@@ -140,7 +140,7 @@
 
     @Test
     fun test_cancel_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(1, 4) // Mark "BCD" as composition
         SetComposingTextEditOp("X", 1).process(eb)
@@ -183,7 +183,7 @@
 
     @Test
     fun test_cursor_position_too_small() {
-        val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+        val eb = EditingBuffer("ABCDE", TextRange(5))
 
         SetComposingTextEditOp("X", -1000).process(eb)
 
@@ -196,7 +196,7 @@
 
     @Test
     fun test_cursor_position_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange(5, 5))
+        val eb = EditingBuffer("ABCDE", TextRange(5))
 
         SetComposingTextEditOp("X", 1000).process(eb)
 
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/SetSelectionEditOpTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/SetSelectionEditOpTest.kt
index 7456f0b..a338de6 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/input/SetSelectionEditOpTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/SetSelectionEditOpTest.kt
@@ -31,7 +31,7 @@
 
     @Test
     fun test_set() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetSelectionEditOp(1, 4).process(eb)
 
@@ -43,7 +43,7 @@
 
     @Test
     fun test_preserve_ongoing_composition() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         eb.setComposition(1, 3)
 
@@ -71,7 +71,7 @@
 
     @Test
     fun test_set_reversed() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetSelectionEditOp(4, 1).process(eb)
 
@@ -83,7 +83,7 @@
 
     @Test
     fun test_set_too_small() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetSelectionEditOp(-1000, -1000).process(eb)
 
@@ -94,7 +94,7 @@
 
     @Test
     fun test_set_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetSelectionEditOp(1000, 1000).process(eb)
 
@@ -105,7 +105,7 @@
 
     @Test
     fun test_set_too_small_too_large() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetSelectionEditOp(-1000, 1000).process(eb)
 
@@ -117,7 +117,7 @@
 
     @Test
     fun test_set_too_small_too_large_reversed() {
-        val eb = EditingBuffer("ABCDE", TextRange(0, 0))
+        val eb = EditingBuffer("ABCDE", TextRange(0))
 
         SetSelectionEditOp(1000, -1000).process(eb)
 
diff --git a/ui/ui-text/src/test/java/androidx/ui/text/TextFieldDelegateTest.kt b/ui/ui-text/src/test/java/androidx/ui/text/TextFieldDelegateTest.kt
index 7d30150..688bec7 100644
--- a/ui/ui-text/src/test/java/androidx/ui/text/TextFieldDelegateTest.kt
+++ b/ui/ui-text/src/test/java/androidx/ui/text/TextFieldDelegateTest.kt
@@ -93,7 +93,7 @@
     @Test
     fun test_on_edit_command() {
         val ops = listOf(CommitTextEditOp("Hello, World", 1))
-        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
 
         whenever(processor.onEditCommands(ops)).thenReturn(dummyEditorState)
 
@@ -111,7 +111,7 @@
     fun test_on_release() {
         val position = Offset(100f, 200f)
         val offset = 10
-        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
         val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
 
         whenever(textLayoutResult.getOffsetForPosition(position)).thenReturn(offset)
@@ -165,7 +165,7 @@
 
     @Test
     fun on_focus() {
-        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
         TextFieldDelegate.onFocus(textInputService, dummyEditorState, processor,
             KeyboardType.Text, ImeAction.Unspecified, onValueChange, onEditorActionPerformed)
         verify(textInputService).startInput(
@@ -230,7 +230,7 @@
         whenever(textLayoutResult.getBoundingBox(any())).thenReturn(dummyRect)
         val dummyPoint = Offset(5f, 6f)
         whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
-        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
         val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
         TextFieldDelegate.notifyFocusedRect(
             dummyEditorState,
@@ -247,7 +247,7 @@
 
     @Test
     fun notify_focused_rect_without_focus() {
-        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
         val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
         TextFieldDelegate.notifyFocusedRect(
             dummyEditorState,
@@ -268,7 +268,7 @@
         whenever(textLayoutResult.getBoundingBox(any())).thenReturn(dummyRect)
         val dummyPoint = Offset(5f, 6f)
         whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
-        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(12, 12))
+        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(12))
         val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
         TextFieldDelegate.notifyFocusedRect(
             dummyEditorState,
@@ -337,7 +337,7 @@
     fun check_on_release_uses_offset_map() {
         val position = Offset(100f, 200f)
         val offset = 10
-        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1, 1))
+        val dummyEditorState = TextFieldValue(text = "Hello, World", selection = TextRange(1))
         val dummyInputSessionToken = 10 // We are not using this value in this test. Just dummy.
 
         whenever(textLayoutResult.getOffsetForPosition(position)).thenReturn(offset)
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt
index b865592..d0a3cd1 100644
--- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt
@@ -199,19 +199,12 @@
             function =
             "androidx.ui.tooling.inspector.LayoutInspectorTreeTest\$buildTree\$1\$1\$1\$1.invoke",
             left = 0.0.dp, top = 18.9.dp, width = 64.0.dp, height = 36.0.dp,
-            children = listOf("Button")
-        )
-        validate(
-            name = "Button",
-            fileName = "Button.kt",
-            function = "androidx.ui.material.ButtonKt.Button",
-            left = 0.0.dp, top = 18.9.dp, width = 64.0.dp, height = 36.0.dp,
             children = listOf("Surface")
         )
         validate(
             name = "Surface",
-            fileName = "Button.kt",
-            function = "androidx.ui.material.ButtonKt\$Button\$1.invoke",
+            fileName = "Surface.kt",
+            function = "androidx.ui.material.SurfaceKt.Surface",
             left = 0.0.dp, top = 18.9.dp, width = 64.0.dp, height = 36.0.dp,
             children = listOf("SurfaceLayout")
         )
@@ -232,7 +225,7 @@
         validate(
             name = "Box",
             fileName = "Button.kt",
-            function = "androidx.ui.material.ButtonKt\$Button\$1\$1.invoke",
+            function = "androidx.ui.material.ButtonKt\$Button\$1.invoke",
             left = 16.0.dp, top = 26.9.dp, width = 32.0.dp, height = 20.0.dp,
             children = listOf("Column")
         )
@@ -260,7 +253,7 @@
         validate(
             name = "ProvideTextStyle",
             fileName = "Button.kt",
-            function = "androidx.ui.material.ButtonKt\$Button\$1\$1\$1.invoke",
+            function = "androidx.ui.material.ButtonKt\$Button\$1\$1.invoke",
             left = 21.8.dp, top = 27.6.dp, width = 20.4.dp, height = 18.9.dp,
             children = listOf("Text")
         )
diff --git a/vectordrawable/vectordrawable-animated/api/api_lint.ignore b/vectordrawable/vectordrawable-animated/api/api_lint.ignore
index ffd0fdf..8bbfad4 100644
--- a/vectordrawable/vectordrawable-animated/api/api_lint.ignore
+++ b/vectordrawable/vectordrawable-animated/api/api_lint.ignore
@@ -59,3 +59,7 @@
     Missing nullability on parameter `dr` in method `unregisterAnimationCallback`
 MissingNullability: androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat#unregisterAnimationCallback(android.graphics.drawable.Drawable, androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback) parameter #1:
     Missing nullability on parameter `callback` in method `unregisterAnimationCallback`
+
+
+NotCloseable: androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat:
+    Classes that release resources (stop()) should implement AutoClosable and CloseGuard: class androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat