Merge "Fix test timeout issues for RecyclerView." into androidx-master-dev
diff --git a/browser/src/androidTest/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManagerTest.java b/browser/src/androidTest/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManagerTest.java
index e9114e1..10fe40f 100644
--- a/browser/src/androidTest/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManagerTest.java
+++ b/browser/src/androidTest/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManagerTest.java
@@ -22,9 +22,7 @@
 
 import android.content.Context;
 import android.net.Uri;
-import android.os.RemoteException;
 
-import androidx.annotation.Nullable;
 import androidx.browser.customtabs.EnableComponentsTestRule;
 import androidx.browser.customtabs.TestActivity;
 import androidx.test.core.app.ApplicationProvider;
@@ -39,6 +37,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -76,36 +75,29 @@
     public void testConnection() {
         final AtomicBoolean connected = new AtomicBoolean();
         boolean delegated = mManager.execute(GOOD_SCOPE, ORIGIN,
-                new TrustedWebActivityServiceConnectionManager.ExecutionCallback() {
-                    @Override
-                    public void onConnected(@Nullable TrustedWebActivityServiceWrapper service)
-                            throws RemoteException {
-                        assertEquals(TestTrustedWebActivityService.SMALL_ICON_ID,
-                                service.getSmallIconId());
-                        connected.set(true);
-                    }
+                service -> {
+                    assertEquals(TestTrustedWebActivityService.SMALL_ICON_ID,
+                            service.getSmallIconId());
+                    connected.set(true);
                 });
         assertTrue(delegated);
 
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return connected.get();
-            }
-        });
+        PollingCheck.waitFor(connected::get);
     }
 
-
-
     @Test
     public void testNoService() {
-        boolean delegated = mManager.execute(BAD_SCOPE, ORIGIN,
-                new TrustedWebActivityServiceConnectionManager.ExecutionCallback() {
-                    @Override
-                    public void onConnected(@Nullable TrustedWebActivityServiceWrapper service)
-                            throws RemoteException {
-                    }
-                });
+        boolean delegated = mManager.execute(BAD_SCOPE, ORIGIN, service -> { });
         assertFalse(delegated);
     }
+
+    @Test
+    public void testMultipleExecutions() {
+        final AtomicInteger count = new AtomicInteger();
+
+        mManager.execute(GOOD_SCOPE, ORIGIN, service -> count.incrementAndGet());
+        mManager.execute(GOOD_SCOPE, ORIGIN, service -> count.incrementAndGet());
+
+        PollingCheck.waitFor(() -> count.get() == 2);
+    }
 }
diff --git a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java
index 6dbcebe..7bb064c 100644
--- a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java
+++ b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityServiceConnectionManager.java
@@ -125,6 +125,14 @@
                 callback.onConnected(mService);
             }
         }
+
+        public void cancel() {
+            for (WrappedCallback callback : mCallbacks) {
+                callback.onConnected(null);
+            }
+            mCallbacks.clear();
+            mConnections.remove(mScope);
+        }
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -259,33 +267,32 @@
 
         final Connection newConnection = new Connection(scope);
         newConnection.addCallback(wrappedCallback);
+        mConnections.put(scope, newConnection);
 
         // Create a new connection.
-        new AsyncTask<Void, Void, Connection>() {
+        new AsyncTask<Void, Void, Boolean>() {
             @Override
-            protected Connection doInBackground(Void... voids) {
+            protected Boolean doInBackground(Void... voids) {
                 try {
                     // We can pass newConnection to bindService here on a background thread because
                     // bindService assures us it will use newConnection on the UI thread.
                     if (mContext.bindService(bindServiceIntent, newConnection,
                             Context.BIND_AUTO_CREATE)) {
-                        return newConnection;
+                        return true;
                     }
 
                     mContext.unbindService(newConnection);
-                    return null;
+                    return false;
                 } catch (SecurityException e) {
                     Log.w(TAG, "SecurityException while binding.", e);
-                    return null;
+                    return false;
                 }
             }
 
             @Override
-            protected void onPostExecute(Connection newConnection) {
-                if (newConnection == null) {
-                    wrappedCallback.onConnected(null);
-                } else {
-                    mConnections.put(scope, newConnection);
+            protected void onPostExecute(Boolean success) {
+                if (!success) {
+                    newConnection.cancel();
                 }
             }
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 8eecd11..48a0153 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -17,12 +17,12 @@
 package androidx.build.dependencies
 
 const val ANDROID_GRADLE_PLUGIN = "com.android.tools.build:gradle:3.4.2"
-const val ANDROIDX_TEST_CORE = "androidx.test:core:1.1.0"
-const val ANDROIDX_TEST_EXT_JUNIT = "androidx.test.ext:junit:1.1.0"
-const val ANDROIDX_TEST_EXT_KTX = "androidx.test.ext:junit-ktx:1.1.0"
-const val ANDROIDX_TEST_MONITOR = "androidx.test:monitor:1.1.1"
-const val ANDROIDX_TEST_RULES = "androidx.test:rules:1.1.0"
-const val ANDROIDX_TEST_RUNNER = "androidx.test:runner:1.1.1"
+const val ANDROIDX_TEST_CORE = "androidx.test:core:1.2.0"
+const val ANDROIDX_TEST_EXT_JUNIT = "androidx.test.ext:junit:1.1.1"
+const val ANDROIDX_TEST_EXT_KTX = "androidx.test.ext:junit-ktx:1.1.1"
+const val ANDROIDX_TEST_MONITOR = "androidx.test:monitor:1.2.0"
+const val ANDROIDX_TEST_RULES = "androidx.test:rules:1.2.0"
+const val ANDROIDX_TEST_RUNNER = "androidx.test:runner:1.2.0"
 const val ANDROIDX_TEST_UIAUTOMATOR = "androidx.test.uiautomator:uiautomator:2.2.0"
 const val AUTO_COMMON = "com.google.auto:auto-common:0.10"
 const val AUTO_VALUE = "com.google.auto.value:auto-value:1.6.3"
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
index cd28c8b..6314055 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import android.app.Instrumentation;
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
@@ -55,7 +56,9 @@
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -85,6 +88,7 @@
             MOCK_ON_PREVIEW_OUTPUT_UPDATE_LISTENER =
             mock(Preview.OnPreviewOutputUpdateListener.class);
 
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private PreviewConfig mDefaultConfig;
     @Mock
     private OnPreviewOutputUpdateListener mMockListener;
@@ -110,6 +114,14 @@
         mDefaultConfig = Preview.DEFAULT_CONFIG.getConfig(LensFacing.BACK);
     }
 
+    @After
+    public void tearDown() throws ExecutionException, InterruptedException {
+        mInstrumentation.runOnMainSync(CameraX::unbindAll);
+
+        // Ensure all cameras are released for the next test
+        CameraX.deinit().get();
+    }
+
     @Test
     @UiThreadTest
     public void getAndSetPreviewSurfaceCallback() {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/compat/CameraDeviceCompatDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/compat/CameraDeviceCompatDeviceTest.java
index dedfcb3..0b93efd 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/compat/CameraDeviceCompatDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/compat/CameraDeviceCompatDeviceTest.java
@@ -143,7 +143,7 @@
     // This test should not run on the main thread since it will block the main thread and
     // deadlock on API <= 28.
     @Test
-    public void canConfigureCaptureSession() throws CameraAccessException, InterruptedException {
+    public void canConfigureCaptureSession() throws InterruptedException, CameraAccessException {
         OutputConfigurationCompat outputConfig = new OutputConfigurationCompat(mSurface);
 
         final Semaphore configureSemaphore = new Semaphore(0);
@@ -168,7 +168,17 @@
                 Collections.singletonList(outputConfig), AsyncTask.THREAD_POOL_EXECUTOR,
                 stateCallback);
 
-        CameraDeviceCompat.createCaptureSession(mCameraDevice, sessionConfig);
+        try {
+            CameraDeviceCompat.createCaptureSession(mCameraDevice, sessionConfig);
+        } catch (CameraAccessException e) {
+            // If the camera has been disconnected during the test (likely due to another process
+            // stealing the camera), then we will skip the test.
+            Assume.assumeTrue("Camera disconnected during test.",
+                    e.getReason() != CameraAccessException.CAMERA_DISCONNECTED);
+
+            // This is not an error we expect should reasonably happen. Rethrow the exception.
+            throw e;
+        }
         configureSemaphore.acquire();
 
         assertThat(configureSucceeded.get()).isTrue();
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureExtenderTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureExtenderTest.java
index 9977b2c..3314ddc 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureExtenderTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureExtenderTest.java
@@ -46,6 +46,7 @@
 import androidx.camera.core.CaptureProcessor;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCaptureConfig;
+import androidx.camera.extensions.ExtensionsManager.EffectMode;
 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.CaptureStageImpl;
 import androidx.camera.extensions.impl.ImageCaptureExtenderImpl;
@@ -209,6 +210,7 @@
             throws CameraInfoUnavailableException, CameraAccessException {
         CameraX.LensFacing lensFacing = CameraX.LensFacing.BACK;
         assumeTrue(CameraUtil.hasCameraWithLensFacing(lensFacing));
+        assumeTrue(ExtensionsManager.isExtensionAvailable(EffectMode.BEAUTY, lensFacing));
         ImageCaptureConfig.Builder configBuilder = new ImageCaptureConfig.Builder().setLensFacing(
                 lensFacing);
 
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolverTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolverTests.kt
new file mode 100644
index 0000000..21c4f7c
--- /dev/null
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/plugins/kotlin/ComposeCallResolverTests.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.plugins.kotlin
+
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.psi.KtCallExpression
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.KtPsiFactory
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import kotlin.reflect.KClass
+
+class ComposeCallResolverTests : AbstractCodegenTest() {
+
+    fun testBasicCallTypes() = assertInterceptions(
+        """
+            import androidx.compose.*
+            import android.widget.TextView
+
+            @Composable fun Foo() {}
+            
+            fun Bar() {}
+
+            @Composable
+            fun test() {
+                <call>Foo()
+                <emit>TextView(text="text")
+                <normal>Bar()
+            }
+        """
+    )
+
+    fun testReceiverScopeCall() = assertInterceptions(
+        """
+            import androidx.compose.*
+
+            @Composable fun Int.Foo() {}
+            
+            @Composable
+            fun test() {
+                val x = 1
+                x.<call>Foo()
+                
+                with(x) {
+                    <call>Foo()
+                }
+            }
+        """
+    )
+
+    fun testInvokeOperatorCall() = assertInterceptions(
+        """
+            import androidx.compose.*
+
+            @Composable operator fun Int.invoke(y: Int) {}
+            
+            @Composable
+            fun test() {
+                val x = 1
+                <call>x(y=10)
+            }
+        """
+    )
+
+
+    private fun <T> setup(block: () -> T): T {
+        val original = ComposeFlags.NEW_CALL_RESOLUTION_INTERCEPTION
+        try {
+            ComposeFlags.NEW_CALL_RESOLUTION_INTERCEPTION = true
+            return block()
+        } finally {
+            ComposeFlags.NEW_CALL_RESOLUTION_INTERCEPTION = original
+        }
+    }
+
+    fun assertInterceptions(srcText: String) = setup {
+        val (text, carets) = extractCarets(srcText)
+
+        val environment = myEnvironment ?: error("Environment not initialized")
+
+        val ktFile = KtPsiFactory(environment.project).createFile(text)
+        val bindingContext = JvmResolveUtil.analyze(
+            ktFile,
+            environment
+        ).bindingContext
+
+        carets.forEachIndexed { index, (offset, calltype) ->
+            val resolvedCall = resolvedCallAtOffset(bindingContext, ktFile, offset)
+                ?: error("No resolved call found at index: $index, offset: $offset. Expected " +
+                    "$calltype.")
+
+            when (calltype) {
+                "<normal>" -> assert(!resolvedCall.isCall() && !resolvedCall.isEmit())
+                "<emit>" -> assert(resolvedCall.isEmit())
+                "<call>" -> assert(resolvedCall.isCall())
+                else -> error("Call type of $calltype not recognized.")
+            }
+
+        }
+    }
+
+    private fun ResolvedCall<*>.isEmit(): Boolean = candidateDescriptor is ComposableEmitDescriptor
+    private fun ResolvedCall<*>.isCall(): Boolean = candidateDescriptor is ComposableFunctionDescriptor
+
+    private val callPattern = Regex("(<normal>)|(<emit>)|(<call>)")
+    private fun extractCarets(text: String): Pair<String, List<Pair<Int, String>>> {
+        val indices = mutableListOf<Pair<Int, String>>()
+        var offset = 0
+        val src = callPattern.replace(text) {
+            indices.add(it.range.first - offset to it.value)
+            offset += it.range.last - it.range.first + 1
+            ""
+        }
+        return src to indices
+    }
+
+    private fun resolvedCallAtOffset(
+        bindingContext: BindingContext,
+        jetFile: KtFile,
+        index: Int
+    ): ResolvedCall<*>? {
+        val element = jetFile.findElementAt(index)!!
+        val callExpression = element.parentOfType<KtCallExpression>()
+        return callExpression?.getResolvedCall(bindingContext)
+    }
+}
+
+private inline fun <reified T : PsiElement> PsiElement.parentOfType(): T? = parentOfType(T::class)
+
+private fun <T : PsiElement> PsiElement.parentOfType(vararg classes: KClass<out T>): T? {
+    return PsiTreeUtil.getParentOfType(this, *classes.map { it.java }.toTypedArray())
+}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableEmitDescriptor.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableEmitDescriptor.kt
new file mode 100644
index 0000000..2aeabbb
--- /dev/null
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableEmitDescriptor.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.plugins.kotlin
+
+import org.jetbrains.kotlin.builtins.DefaultBuiltIns
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.Modality
+import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
+import org.jetbrains.kotlin.descriptors.SourceElement
+import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
+import org.jetbrains.kotlin.descriptors.Visibilities
+import org.jetbrains.kotlin.descriptors.annotations.Annotations
+import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
+import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.resolve.calls.tower.ImplicitScopeTower
+import org.jetbrains.kotlin.types.replace
+import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
+
+class ComposableEmitDescriptor(
+    val ktxCall: ResolvedKtxElementCall,
+    containingDeclaration: DeclarationDescriptor,
+    original: SimpleFunctionDescriptor?,
+    annotations: Annotations,
+    name: Name,
+    kind: CallableMemberDescriptor.Kind,
+    source: SourceElement
+): SimpleFunctionDescriptorImpl(
+    containingDeclaration,
+    original,
+    annotations,
+    name,
+    kind,
+    source
+) {
+
+    companion object {
+        fun fromKtxCall(
+            ktxCall: ResolvedKtxElementCall,
+            scopeTower: ImplicitScopeTower,
+            name: Name
+        ): ComposableEmitDescriptor? {
+
+            val builtIns = DefaultBuiltIns.Instance
+            val emitOrCall = ktxCall.emitOrCall
+            if (emitOrCall !is EmitCallNode) {
+                return null
+            }
+
+            val resolvedCall = emitOrCall.primaryCall ?: return null
+
+            val original = resolvedCall.candidateDescriptor
+                            as? SimpleFunctionDescriptor
+            val descriptor = ComposableEmitDescriptor(
+                ktxCall,
+                ktxCall.infixOrCall!!.candidateDescriptor.containingDeclaration,
+                original,
+                Annotations.EMPTY,
+                name,
+                CallableMemberDescriptor.Kind.SYNTHESIZED,
+                SourceElement.NO_SOURCE
+            )
+
+            val valueArgs = mutableListOf<ValueParameterDescriptor>()
+
+            ktxCall.usedAttributes.forEachIndexed { index, attributeInfo ->
+                valueArgs.add(
+                    ValueParameterDescriptorImpl(
+                        descriptor, null, index,
+                        Annotations.EMPTY,
+                        Name.identifier(
+                            if (attributeInfo.name == CHILDREN_KEY)
+                                attributeInfo.descriptor.name.identifier
+                            else attributeInfo.name
+                        ),
+                        attributeInfo.type, false,
+                        false,
+                        false, null,
+                        SourceElement.NO_SOURCE
+                    )
+                )
+            }
+
+            val unitLambdaType = builtIns.getFunction(
+                0
+            ).defaultType.replace(
+                listOf(builtIns.unitType.asTypeProjection())
+            ).makeComposable(scopeTower.module)
+            (emitOrCall as? EmitCallNode)?.inlineChildren?.let {
+                valueArgs.add(
+                    ValueParameterDescriptorImpl(
+                        descriptor, null, valueArgs.size,
+                        Annotations.EMPTY,
+                        Name.identifier("\$CHILDREN"),
+                        unitLambdaType, false,
+                        false,
+                        false, null,
+                        SourceElement.NO_SOURCE
+                    )
+                )
+            }
+
+            descriptor.initialize(
+                null,
+                null,
+                mutableListOf(),
+                valueArgs,
+                builtIns.unitType,
+                Modality.FINAL,
+                Visibilities.DEFAULT_VISIBILITY
+            )
+
+            return descriptor
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableFunctionDescriptor.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableFunctionDescriptor.kt
new file mode 100644
index 0000000..d5ee11e
--- /dev/null
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposableFunctionDescriptor.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.plugins.kotlin
+
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+
+class ComposableFunctionDescriptor(val underlyingDescriptor: FunctionDescriptor)
+    : FunctionDescriptor by underlyingDescriptor
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt
index 37ef3ea..9aaba1e 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolutionInterceptorExtension.kt
@@ -35,6 +35,7 @@
 import org.jetbrains.kotlin.psi.KtExpression
 import org.jetbrains.kotlin.psi.KtFunction
 import org.jetbrains.kotlin.psi.KtLambdaArgument
+import org.jetbrains.kotlin.psi.KtPsiFactory
 import org.jetbrains.kotlin.resolve.BindingContext
 import org.jetbrains.kotlin.resolve.calls.CandidateResolver
 import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
@@ -69,6 +70,25 @@
         name: Name,
         location: LookupLocation
     ): Collection<FunctionDescriptor> {
+        if (ComposeFlags.NEW_CALL_RESOLUTION_INTERCEPTION) {
+            val callResolver = (scopeTower as NewResolutionOldInference.ImplicitScopeTowerImpl).callResolver
+            val element = resolutionContext.call.callElement as KtExpression
+            val project = element.project
+            val psiFactory = KtPsiFactory(project, markGenerated = false)
+
+            return ComposeCallResolver(
+                callResolver,
+                project,
+                psiFactory
+            ).interceptCandidates(
+                candidates,
+                scopeTower,
+                resolutionContext,
+                resolutionScope,
+                name,
+                location
+            )
+        }
         if (candidates.isEmpty()) return candidates
         if (KtxCallResolver.resolving.get().get()) return candidates
 
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolver.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolver.kt
new file mode 100644
index 0000000..e103e60
--- /dev/null
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeCallResolver.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.plugins.kotlin
+
+import com.intellij.openapi.project.Project
+import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.VariableDescriptor
+import org.jetbrains.kotlin.extensions.CallResolutionInterceptorExtension
+import org.jetbrains.kotlin.incremental.components.LookupLocation
+import org.jetbrains.kotlin.incremental.components.NoLookupLocation
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtPsiFactory
+import org.jetbrains.kotlin.resolve.calls.CallResolver
+import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
+import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache
+import org.jetbrains.kotlin.resolve.calls.tower.ImplicitScopeTower
+import org.jetbrains.kotlin.resolve.scopes.LexicalScope
+import org.jetbrains.kotlin.resolve.scopes.ResolutionScope
+import org.jetbrains.kotlin.resolve.scopes.utils.collectVariables
+import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
+
+
+class ComposeCallResolver(
+    private val callResolver: CallResolver,
+    private val project: Project,
+    private val psiFactory: KtPsiFactory
+) {
+
+    @Suppress("UNUSED_PARAMETER")
+    fun interceptCandidates(
+        candidates: Collection<FunctionDescriptor>,
+        scopeTower: ImplicitScopeTower,
+        resolutionContext: BasicCallResolutionContext,
+        resolutionScope: ResolutionScope,
+        name: Name,
+        location: LookupLocation
+    ): Collection<FunctionDescriptor> {
+        if (candidates.isEmpty()) return candidates
+
+        val composables = mutableListOf<FunctionDescriptor>()
+        val nonComposables = mutableListOf<FunctionDescriptor>()
+        val constructors = mutableListOf<ConstructorDescriptor>()
+
+        var needToLookupComposer = false
+
+        for (candidate in candidates) {
+            if (candidate.hasComposableAnnotation()) {
+                needToLookupComposer = true
+                composables.add(candidate)
+            } else {
+                nonComposables.add(candidate)
+            }
+            if (candidate is ConstructorDescriptor) {
+                needToLookupComposer = true
+                constructors.add(candidate)
+            }
+        }
+
+        // If none of the candidates are composable or constructors, then it's unnecessary for us
+        // to do any work at all, since it will never be anything we intercept
+        if (!needToLookupComposer) return candidates
+
+        // TODO(lmr): refactor/removal of ktxcallresolver so we don't need to do this!!!
+        // THREAD LOCAL!!!!
+        if (KtxCallResolver.resolving.get().get()) return candidates
+
+        // use the scope tower to find any variable that would resolve with "composer" in scope.
+        val composer = scopeTower
+            .lexicalScope
+            .collectVariables(KtxNameConventions.COMPOSER, location)
+            .firstOrNull()
+
+        // If there is no composer in scope, then we cannot intercept. This means that we need to
+        // remove any @Composable from the candidates
+        if (composer == null) {
+            return nonComposables
+        }
+
+        // TODO(lmr): figure out if we need to do something here
+        // We might decide there are some composers that are not "valid", ie, I shouldn't be able
+        // to call a composable if I just have `val composer = Unit` in scope... but with this
+        // logic, you'd be able to. This will get refined as we pass a composer as a variable.
+        val isValidComposer = true
+
+        // If there are no constructors, then all of the candidates are either composables or
+        // non-composable functions, and we follow normal resolution rules.
+        if (isValidComposer && constructors.isEmpty()) {
+            // we wrap the composable descriptors into a ComposableFunctionDescriptor so we know
+            // to intercept it in the backend.
+            return nonComposables + composables.map { ComposableFunctionDescriptor(it) }
+        }
+
+        // If we made it this far, we need to check and see if the constructors qualify as emit
+        // calls instead of constructor calls.  First, we need to look at the composer to see
+        // what kinds of "emittables" it accepts.
+        // We cache the metadata into a writeable slice based on the descriptor
+        val emitMetadata = ComposerEmitMetadata.getOrBuild(
+            composer,
+            callResolver,
+            psiFactory,
+            resolutionContext
+        )
+
+        val hasEmittableCandidate = constructors.any { emitMetadata.isEmittable(it.returnType) }
+
+        // if none of the constructors are emittables, then all of the candidates are valid
+        if (!hasEmittableCandidate) {
+            return nonComposables + composables.map { ComposableFunctionDescriptor(it) }
+        }
+
+        // since some of the constructors are emittables, we fall back to resolving using the
+        // ktx call resolver. This needs to be refactored to be simpler, but this should work as
+        // a starting point.
+        //
+        // TODO(lmr): refactor this to remove KtxCallResolver and the use of the facade
+        // THREAD LOCAL!!!!
+        val facade = CallResolutionInterceptorExtension.facade.get().peek()
+        val ktxCallResolver = KtxCallResolver(
+            callResolver,
+            facade,
+            project,
+            ComposableAnnotationChecker.get(project)
+        )
+
+        val context = ExpressionTypingContext.newContext(
+            resolutionContext.trace,
+            resolutionContext.scope,
+            resolutionContext.dataFlowInfo,
+            resolutionContext.expectedType,
+            resolutionContext.languageVersionSettings,
+            resolutionContext.dataFlowValueFactory
+        )
+
+        val call = resolutionContext.call
+
+        val element = call.callElement as KtExpression
+
+        val temporaryTraceForKtxCall =
+            TemporaryTraceAndCache.create(
+                context,
+                "trace to resolve ktx call", element
+            )
+
+        val temporaryForKtxCall = context.replaceTraceAndCache(temporaryTraceForKtxCall)
+
+        ktxCallResolver.initializeFromCall(call, temporaryForKtxCall)
+
+        val resolvedKtxElementCall = ktxCallResolver.resolveFromCall(
+            call,
+            temporaryForKtxCall
+        )
+
+        val result = ComposableEmitDescriptor.fromKtxCall(
+            resolvedKtxElementCall,
+            scopeTower,
+            name
+        )
+
+        if (result == null) {
+            return nonComposables +
+                    composables.map { ComposableFunctionDescriptor(it) } +
+                    constructors.filter { !emitMetadata.isEmittable(it.returnType) }
+        }
+
+        // TODO(lmr): deal with this RESTART_CALLS_NEEDED stuff
+        // Once we know we have a valid binding to a composable function call see if the scope need
+        // the startRestartGroup and endRestartGroup information
+
+        return listOf(result)
+    }
+
+}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFlags.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFlags.kt
index ac6a98f..986ff92 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFlags.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposeFlags.kt
@@ -19,4 +19,5 @@
 object ComposeFlags {
     var FRAMED_COMPONENTS = false
     var FRAMED_MODEL_CLASSES = true
+    var NEW_CALL_RESOLUTION_INTERCEPTION = false
 }
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposerEmitMetadata.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposerEmitMetadata.kt
new file mode 100644
index 0000000..8566788
--- /dev/null
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/ComposerEmitMetadata.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.plugins.kotlin
+
+import androidx.compose.plugins.kotlin.analysis.ComposeWritableSlices
+import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
+import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
+import org.jetbrains.kotlin.builtins.isFunctionTypeOrSubtype
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
+import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
+import org.jetbrains.kotlin.descriptors.VariableDescriptor
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.KtPsiFactory
+import org.jetbrains.kotlin.resolve.calls.CallResolver
+import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
+import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
+import org.jetbrains.kotlin.resolve.calls.model.DataFlowInfoForArgumentsImpl
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver
+import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.isError
+import org.jetbrains.kotlin.types.typeUtil.isNothingOrNullableNothing
+import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
+
+class ComposerEmitMetadata(
+    // Set of valid upper bound types that were defined on the composer that can't have children
+    // For android, this should be [View]
+    private val emitSimpleUpperBoundTypes: Set<KotlinType>,
+    // Set of valid upper bound types that were defined on the composer that can have children.
+    // For android, this would be [ViewGroup]
+    private val emitCompoundUpperBoundTypes: Set<KotlinType>,
+    // The specification for `emit` on a composer allows for the `ctor` parameter to be a function type
+    // with any number of parameters. We allow for these parameters to be used as parameters in the
+    // Constructors that are emitted with a KTX tag. These parameters can be overridden with attributes
+    // in the KTX tag, but if there are required parameters with a type that matches one declared in the
+    // ctor parameter, we will resolve it automatically with the value passed in the `ctor` lambda.
+    //
+    // In order to do this resolution, we store a list of pairs of "upper bounds" to parameter types. For example,
+    // the following emit call:
+    //
+    //      fun <T : View> emit(key: Any, ctor: (context: Context) -> T, update: U<T>.() -> Unit)
+    //
+    // would produce a Pair of [View] to [Context]
+    private val emittableTypeToImplicitCtorTypes: List<Pair<List<KotlinType>, Set<KotlinType>>>
+) {
+
+    companion object {
+        private fun resolveComposerMethodCandidates(
+            name: Name,
+            context: BasicCallResolutionContext,
+            composerType: KotlinType,
+            callResolver: CallResolver,
+            psiFactory: KtPsiFactory
+        ): Collection<ResolvedCall<*>> {
+            val calleeExpression = psiFactory.createSimpleName(name.asString())
+
+            val methodCall = makeCall(
+                callElement = context.call.callElement,
+                calleeExpression = calleeExpression,
+                receiver = TransientReceiver(
+                    composerType
+                )
+            )
+
+            val contextForVariable =
+                BasicCallResolutionContext.create(
+                    context,
+                    methodCall,
+                    CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
+                    DataFlowInfoForArgumentsImpl(
+                        context.dataFlowInfo,
+                        methodCall
+                    )
+                )
+
+            val results = callResolver.resolveCallWithGivenName(
+                // it's important that we use "collectAllCandidates" so that extension functions get included
+                contextForVariable.replaceCollectAllCandidates(true),
+                methodCall,
+                calleeExpression,
+                name
+            )
+
+            return results.allCandidates ?: emptyList()
+        }
+
+        fun build(
+            composerType: KotlinType,
+            callResolver: CallResolver,
+            psiFactory: KtPsiFactory,
+            resolutionContext: BasicCallResolutionContext
+        ): ComposerEmitMetadata {
+            val emitSimpleUpperBoundTypes = mutableSetOf<KotlinType>()
+            val emitCompoundUpperBoundTypes = mutableSetOf<KotlinType>()
+            val emittableTypeToImplicitCtorTypes = mutableListOf<Pair<List<KotlinType>, Set<KotlinType>>>()
+
+            val emitCandidates = resolveComposerMethodCandidates(
+                KtxNameConventions.EMIT,
+                resolutionContext,
+                composerType,
+                callResolver,
+                psiFactory
+            )
+
+            for (candidate in emitCandidates.map { it.candidateDescriptor }) {
+                if (candidate.name != KtxNameConventions.EMIT) continue
+                if (candidate !is SimpleFunctionDescriptor) continue
+                val params = candidate.valueParameters
+                // NOTE(lmr): we could report diagnostics on some of these? it seems strange to emit diagnostics about a function
+                // that is not necessarily being used though. I think it's probably better to just ignore them here.
+
+                // the signature of emit that we are looking for has 3 or 4 parameters
+                if (params.size < 3 || params.size > 4) continue
+                val ctorParam = params.find { it.name == KtxNameConventions.EMIT_CTOR_PARAMETER }
+                    ?: continue
+                if (!ctorParam.type.isFunctionTypeOrSubtype) continue
+
+                // the return type from the ctor param is the "upper bound" of the node type. It will often be a generic type with constraints.
+                val upperBounds = ctorParam.type.getReturnTypeFromFunctionType().upperBounds()
+
+                // the ctor param can have parameters itself, which we interpret as implicit parameter types that the composer knows how to
+                // automatically provide to the component. In the case of Android Views, this is how we automatically provide Context.
+                val implicitParamTypes = ctorParam.type.getValueParameterTypesFromFunctionType().map {
+                    it.type
+                }
+
+                for (implicitType in implicitParamTypes) {
+                    emittableTypeToImplicitCtorTypes.add(upperBounds to implicitParamTypes.toSet())
+                }
+
+                emitSimpleUpperBoundTypes.addAll(upperBounds)
+
+                if (params.any { it.name == KtxNameConventions.EMIT_CHILDREN_PARAMETER }) {
+                    emitCompoundUpperBoundTypes.addAll(upperBounds)
+                }
+            }
+
+            return ComposerEmitMetadata(
+                emitSimpleUpperBoundTypes,
+                emitCompoundUpperBoundTypes,
+                emittableTypeToImplicitCtorTypes
+            )
+        }
+
+        fun getOrBuild(
+            descriptor: VariableDescriptor,
+            callResolver: CallResolver,
+            psiFactory: KtPsiFactory,
+            resolutionContext: BasicCallResolutionContext
+        ): ComposerEmitMetadata {
+            val meta = resolutionContext.trace.bindingContext[ComposeWritableSlices.COMPOSER_EMIT_METADATA, descriptor]
+            return if (meta == null) {
+                val built = build(descriptor.type, callResolver, psiFactory, resolutionContext)
+                resolutionContext.trace.record(ComposeWritableSlices.COMPOSER_EMIT_METADATA, descriptor, built)
+                built
+            } else {
+                meta
+            }
+        }
+    }
+
+    fun isEmittable(type: KotlinType) =
+        !type.isError && !type.isNothingOrNullableNothing() && emitSimpleUpperBoundTypes.any {
+            type.isSubtypeOf(it)
+        }
+
+    fun isCompoundEmittable(type: KotlinType) = !type.isError &&
+            !type.isNothingOrNullableNothing() &&
+            emitCompoundUpperBoundTypes.any {
+                type.isSubtypeOf(it)
+            }
+
+    fun isImplicitConstructorParam(
+        param: ValueParameterDescriptor,
+        fn: CallableDescriptor
+    ): Boolean {
+        val returnType = fn.returnType ?: return false
+        val paramType = param.type
+        for ((upperBounds, implicitTypes) in emittableTypeToImplicitCtorTypes) {
+            if (!implicitTypes.any { it.isSubtypeOf(paramType) }) continue
+            if (!returnType.satisfiesConstraintsOf(upperBounds)) continue
+            return true
+        }
+        return false
+    }
+}
\ No newline at end of file
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt
index 1e55ba3..c6f1082 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/KtxCallResolver.kt
@@ -3347,7 +3347,7 @@
 
 private val builtIns = DefaultBuiltIns.Instance
 
-private fun makeCall(
+fun makeCall(
     callElement: KtElement,
     calleeExpression: KtExpression? = null,
     valueArguments: List<ValueArgument> = emptyList(),
@@ -3439,7 +3439,7 @@
     return T.upperBounds.all { isSubtypeOf(it) }
 }
 
-private fun KotlinType.satisfiesConstraintsOf(bounds: List<KotlinType>): Boolean {
+fun KotlinType.satisfiesConstraintsOf(bounds: List<KotlinType>): Boolean {
     return bounds.all { isSubtypeOf(it) }
 }
 
@@ -3502,7 +3502,7 @@
     else -> emptyList()
 }
 
-private fun KotlinType.upperBounds(): List<KotlinType> {
+fun KotlinType.upperBounds(): List<KotlinType> {
     return if (isTypeParameter()) {
         TypeUtils.getTypeParameterDescriptorOrNull(this)?.upperBounds ?: emptyList()
     } else {
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
index 3fce096..bb8afe3 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/plugins/kotlin/analysis/ComposeWritableSlices.kt
@@ -1,11 +1,13 @@
 package androidx.compose.plugins.kotlin.analysis
 
 import androidx.compose.plugins.kotlin.ComposableAnnotationChecker
+import androidx.compose.plugins.kotlin.ComposerEmitMetadata
 import androidx.compose.plugins.kotlin.ResolvedKtxElementCall
 import androidx.compose.plugins.kotlin.ResolvedRestartCalls
 import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
 import org.jetbrains.kotlin.descriptors.FunctionDescriptor
 import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
+import org.jetbrains.kotlin.descriptors.VariableDescriptor
 import org.jetbrains.kotlin.psi.KtElement
 import org.jetbrains.kotlin.psi.KtReferenceExpression
 import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -36,6 +38,8 @@
         BasicWritableSlice(RewritePolicy.DO_NOTHING)
     val RESTART_CALLS: WritableSlice<SimpleFunctionDescriptor, ResolvedRestartCalls> =
         BasicWritableSlice(RewritePolicy.DO_NOTHING)
+    val COMPOSER_EMIT_METADATA: WritableSlice<VariableDescriptor, ComposerEmitMetadata> =
+        BasicWritableSlice(RewritePolicy.DO_NOTHING)
 }
 
 private val REWRITES_ALLOWED = object : RewritePolicy {
diff --git a/compose/compose-runtime/integration-tests/samples/build.gradle b/compose/compose-runtime/integration-tests/samples/build.gradle
new file mode 100644
index 0000000..0789dfd
--- /dev/null
+++ b/compose/compose-runtime/integration-tests/samples/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("AndroidXUiPlugin")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin")
+
+    implementation(KOTLIN_COMPOSE_STDLIB)
+
+    implementation project(":annotation:annotation-sampled")
+    implementation project(":compose:compose-runtime")
+    implementation project(":ui:ui-android-view-non-ir")
+}
diff --git a/work/workmanager-foreground/src/main/AndroidManifest.xml b/compose/compose-runtime/integration-tests/samples/src/main/AndroidManifest.xml
similarity index 65%
rename from work/workmanager-foreground/src/main/AndroidManifest.xml
rename to compose/compose-runtime/integration-tests/samples/src/main/AndroidManifest.xml
index 833ba7d..6fc638b 100644
--- a/work/workmanager-foreground/src/main/AndroidManifest.xml
+++ b/compose/compose-runtime/integration-tests/samples/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright (C) 2016 The Android Open Source Project
+  ~ Copyright (C) 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.
@@ -11,12 +11,6 @@
   ~ 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.
+  ~ limitations under the License
   -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="androidx.work.foreground">
-
-    <permission android:name="android.permission.FOREGROUND_SERVICE" />
-
-</manifest>
+<manifest package="androidx.compose.samples" />
diff --git a/compose/compose-runtime/integration-tests/samples/src/main/java/androidx/compose/samples/EffectSamples.kt b/compose/compose-runtime/integration-tests/samples/src/main/java/androidx/compose/samples/EffectSamples.kt
new file mode 100644
index 0000000..1162b34
--- /dev/null
+++ b/compose/compose-runtime/integration-tests/samples/src/main/java/androidx/compose/samples/EffectSamples.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.samples
+
+import android.widget.Button
+import android.widget.TextView
+import androidx.annotation.Sampled
+import androidx.compose.Composable
+import androidx.compose.State
+import androidx.compose.composer
+import androidx.compose.effectOf
+import androidx.compose.key
+import androidx.compose.onCommit
+import androidx.compose.state
+import androidx.compose.stateFor
+import androidx.compose.unaryPlus
+import androidx.ui.androidview.adapters.setOnClick
+
+@Suppress("unused")
+@Sampled
+@Composable
+fun observeUserSample() {
+    fun observeUser(userId: Int) = effectOf<User?> {
+        val user = +stateFor<User?>(userId) { null }
+        +onCommit(userId) {
+            val subscription = UserAPI.subscribeToUser(userId) {
+                user.value = it
+            }
+            onDispose {
+                subscription.unsubscribe()
+            }
+        }
+        user.value
+    }
+}
+
+@Sampled
+@Composable
+fun oneInputKeySample() {
+    for (element in elements) {
+        val selected by +key<State<Boolean>>(element.id) { +state { false } }
+        ListItem(item = element, selected = selected)
+    }
+}
+
+@Sampled
+@Composable
+fun twoInputsKeySample() {
+    for (element in elements) {
+        val selected by +key<State<Boolean>>(element.id, parentId) { +state { false } }
+        ListItem(item = element, selected = selected)
+    }
+}
+
+@Sampled
+@Composable
+fun SimpleStateSample() {
+    val count = +state { 0 }
+
+    TextView(text = "You clicked ${count.value} times")
+    Button(text = "Click me", onClick = { count.value++ })
+}
+
+@Sampled
+@Composable
+fun DestructuredStateSample() {
+    val (count, setCount) = +state { 0 }
+
+    TextView(text = "You clicked $count times")
+    Button(text = "Click me", onClick = { setCount(count + 1) })
+}
+
+// TODO: operator assignment for local delegated properties is currently not supported
+// https://github.com/JetBrains/kotlin/blob/11f3c4b03f40460160c1f23b634941a867fd817b/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java#L2268
+@Suppress("ReplaceWithOperatorAssignment")
+@Sampled
+@Composable
+fun DelegatedStateSample() {
+    var count by +state { 0 }
+
+    TextView(text = "You clicked $count times")
+    Button(text = "Click me", onClick = { count = count + 1 })
+}
+
+private class User
+private class Subscription {
+    fun unsubscribe() {}
+}
+
+@Suppress("UNUSED_PARAMETER")
+private object UserAPI {
+    fun subscribeToUser(userId: Int, user: (User) -> Unit): Subscription {
+        return Subscription()
+    }
+}
+
+private val elements = listOf<Element>()
+
+private class Element(val id: Int)
+
+@Suppress("UNUSED_PARAMETER")
+private fun ListItem(item: Any, selected: Boolean) {}
+
+private const val parentId = 0
diff --git a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Effects.kt b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Effects.kt
index 5f82a69..6c21d31 100644
--- a/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Effects.kt
+++ b/compose/compose-runtime/src/commonMain/kotlin/androidx/compose/Effects.kt
@@ -120,18 +120,7 @@
  *
  * For example, a custom `observeUser` Effect might look something like this:
  *
- *    fun observeUser(userId: Int) = effectOf<User?> {
- *        val user = +stateFor<User?>(userId) { null }
- *        +onCommit(userId) {
- *            val subscription = UserAPI.subscribeToUser(userId) {
- *                user.value = it
- *            }
- *            onDispose {
- *                subscription.unsubscribe()
- *            }
- *        }
- *        user.value
- *    }
+ * @sample androidx.compose.samples.observeUserSample
  *
  * @param block the executable block of code that returns the value of the effect, run in the context of the Effect
  */
@@ -223,11 +212,7 @@
  * Compose determine which effects should be removed or added.  Any other effects can be created inside of the block of the
  * key effect.
  *
- * Example:
- *
- *     for (el in elements)
- *       val selected = +key(el.id) { +state { false } }
- *       ListItem(item=el, selected=selected)
+ * @sample androidx.compose.samples.oneInputKeySample
  *
  * @param v1 The value to use as the key. This will be compared to its previous value using `Object.equals`
  * @param block The block to execute other effects in
@@ -244,11 +229,7 @@
  *
  * A compound key will be created from both [v1] and [v2].
  *
- * Example:
- *
- *     for (el in elements)
- *       val selected = +key(el.id, parentId) { +state { false } }
- *       ListItem(item=el, selected=selected)
+ * @sample androidx.compose.samples.twoInputsKeySample
  *
  * @param v1 The first value to use as a key. This will be compared to its previous value using `Object.equals`
  * @param v2 The second value to use as a key. This will be compared to its previous value using `Object.equals`
@@ -264,11 +245,7 @@
  * Compose determine which effects should be removed or added.  Any other effects can be created inside of the block of the key
  * effect.
  *
- * Example:
- *
- *     for (el in elements)
- *       val selected = +key(el.id, parentId) { +state { false } }
- *       ListItem(item=el, selected=selected)
+ * @sample androidx.compose.samples.twoInputsKeySample
  *
  * @param inputs The set of values to be used to create a compound key. This will be compared to its previous value using `Object.equals`
  * @param block The block to execute other effects in
@@ -554,49 +531,15 @@
  * The [State] class can be used several different ways. For example, the most basic way is to store the returned state
  * value into a local immutable variable, and then set the [State.value] property on it.
  *
- * Example:
- *
- *    @Composable
- *    fun Example() {
- *        val count = +state { 0 }
- *
- *        TextView(text="You clicked ${count.value} times")
- *        Button(
- *            text="Click me",
- *            onClick={ count.value += 1 }
- *        )
- *    }
+ * @sample androidx.compose.samples.SimpleStateSample
  *
  * Additionally, you can destructure the [State] object into a value and a "setter" function.
  *
- * Example:
- *
- *    @Composable
- *    fun Example() {
- *        val (count, setCount) = +state { 0 }
- *
- *        TextView(text="You clicked ${count} times")
- *        Button(
- *            text="Click me",
- *            onClick={ setCount(count + 1) }
- *        )
- *    }
+ * @sample androidx.compose.samples.DestructuredStateSample
  *
  * Finally, the [State] instance can be used as a variable delegate to a local mutable variable.
  *
- * Example:
- *
- *    @Composable
- *    fun Example() {
- *        var count by +state { 0 }
- *
- *        TextView(text="You clicked $count times")
- *        Button(
- *            text="Click me",
- *            onClick={ count += 1 }
- *        )
- *    }
- *
+ * @sample androidx.compose.samples.DelegatedStateSample
  *
  * @param init A factory function to create the initial value of this state
  * @return An [Model] instance of [State] that wraps the value.
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 45effa6..8665c61 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1565,8 +1565,10 @@
         }
 
         if (f.mState != newState) {
-            Log.w(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
-                    + "expected state " + newState + " found " + f.mState);
+            if (isLoggingEnabled(Log.DEBUG)) {
+                Log.d(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
+                        + "expected state " + newState + " found " + f.mState);
+            }
             f.mState = newState;
         }
     }
diff --git a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java
index 9a1a494..d7ed4e1 100644
--- a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java
+++ b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.ReportFragment.ActivityInitializationListener;
 
@@ -159,25 +160,38 @@
         Application app = (Application) context.getApplicationContext();
         app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
             @Override
+            public void onActivityPreCreated(@NonNull Activity activity,
+                    @Nullable Bundle savedInstanceState) {
+                // We need the ProcessLifecycleOwner to get ON_START and ON_RESUME precisely
+                // before the first activity gets its LifecycleOwner started/resumed.
+                // The activity's LifecycleOwner gets started/resumed via an activity registered
+                // callback added in onCreate(). By adding our own activity registered callback in
+                // onActivityPreCreated(), we get our callbacks first while still having the
+                // right relative order compared to the Activity's onStart()/onResume() callbacks.
+                activity.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
+                    @Override
+                    public void onActivityPostStarted(@NonNull Activity activity) {
+                        activityStarted();
+                    }
+
+                    @Override
+                    public void onActivityPostResumed(@NonNull Activity activity) {
+                        activityResumed();
+                    }
+                });
+            }
+
+            @Override
             public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                 // Only use ReportFragment pre API 29 - after that, we can use the
-                // onActivityPostStarted and onActivityPostResumed callbacks directly
+                // onActivityPostStarted and onActivityPostResumed callbacks registered in
+                // onActivityPreCreated()
                 if (Build.VERSION.SDK_INT < 29) {
                     ReportFragment.get(activity).setProcessListener(mInitializationListener);
                 }
             }
 
             @Override
-            public void onActivityPostStarted(@NonNull Activity activity) {
-                activityStarted();
-            }
-
-            @Override
-            public void onActivityPostResumed(@NonNull Activity activity) {
-                activityResumed();
-            }
-
-            @Override
             public void onActivityPaused(Activity activity) {
                 activityPaused();
             }
diff --git a/settings.gradle b/settings.gradle
index da73627..ee02545 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -248,7 +248,6 @@
 includeProject(":webkit", "webkit")
 includeProject(":webkit:integration-tests:testapp", "webkit/integration-tests/testapp")
 includeProject(":work:work-runtime", "work/workmanager")
-includeProject(":work:work-foreground", "work/workmanager-foreground")
 includeProject(":work:work-gcm", "work/workmanager-gcm")
 includeProject(":work:work-runtime-ktx", "work/workmanager-ktx")
 includeProject(":work:work-rxjava2", "work/workmanager-rxjava2")
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/text/ParagraphBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/text/ParagraphBenchmark.kt
index d60e197..46a0df6 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/text/ParagraphBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/text/ParagraphBenchmark.kt
@@ -64,53 +64,98 @@
         }
     }
 
-    private fun paragraph(textGenerator: RandomTextGenerator): Paragraph {
+    private fun text(textGenerator: RandomTextGenerator): AnnotatedString {
         val text = textGenerator.nextParagraph(textLength)
-
-        val styles = if (textType == TextType.StyledText) {
+        val textStyles = if (textType == TextType.StyledText) {
             textGenerator.createStyles(text)
         } else {
             listOf()
         }
+        return AnnotatedString(text = text, textStyles = textStyles)
+    }
+
+    private fun paragraph(
+        text: String,
+        textStyles: List<AnnotatedString.Item<TextStyle>>,
+        constraints: ParagraphConstraints
+    ): Paragraph {
         return Paragraph(
+            paragraphIntrinsics = paragraphIntrinsics(text, textStyles),
+            constraints = constraints
+        )
+    }
+
+    private fun paragraphIntrinsics(
+        textGenerator: RandomTextGenerator
+    ): ParagraphIntrinsics {
+        val annotatedString = text(textGenerator)
+        return paragraphIntrinsics(
+            text = annotatedString.text,
+            textStyles = annotatedString.textStyles
+        )
+    }
+
+    private fun paragraphIntrinsics(
+        text: String,
+        textStyles: List<AnnotatedString.Item<TextStyle>>
+    ): ParagraphIntrinsics {
+        return ParagraphIntrinsics(
             text = text,
             density = Density(density = 1f),
             style = TextStyle(fontSize = 12.sp),
             paragraphStyle = ParagraphStyle(),
             resourceLoader = resourceLoader,
-            textStyles = styles,
+            textStyles = textStyles,
             layoutDirection = LayoutDirection.Ltr
         )
     }
 
     @Test
     fun minIntrinsicWidth() {
-        benchmarkRule.measureRepeated {
-            val paragraph = runWithTimingDisabled {
-                textBenchmarkRule.generator { textGenerator ->
-                    paragraph(textGenerator)
+        textBenchmarkRule.generator { textGenerator ->
+            benchmarkRule.measureRepeated {
+                val intrinsics = runWithTimingDisabled {
+                    paragraphIntrinsics(textGenerator)
                 }
-            }
 
-            paragraph.minIntrinsicWidth
+                intrinsics.minIntrinsicWidth
+            }
         }
     }
 
     @Test
-    fun layout() {
-        benchmarkRule.measureRepeated {
-            val pair = runWithTimingDisabled {
-                textBenchmarkRule.generator { textGenerator ->
-                    val paragraph = paragraph(textGenerator)
-                    paragraph.layout(ParagraphConstraints(Float.MAX_VALUE))
+    fun maxIntrinsicWidth() {
+        textBenchmarkRule.generator { textGenerator ->
+            benchmarkRule.measureRepeated {
+                val intrinsics = runWithTimingDisabled {
+                    paragraphIntrinsics(textGenerator)
+                }
+
+                intrinsics.maxIntrinsicWidth
+            }
+        }
+    }
+
+    @Test
+    fun construct() {
+        textBenchmarkRule.generator { textGenerator ->
+            benchmarkRule.measureRepeated {
+                val textAndWidth = runWithTimingDisabled {
+                    val intrinsics = paragraphIntrinsics(textGenerator)
                     // create a new paragraph and use a smaller width to get
                     // some line breaking in the result
-                    Pair(paragraph(textGenerator), paragraph.maxIntrinsicWidth / 4f)
+                    Pair(
+                        text(textGenerator),
+                        intrinsics.maxIntrinsicWidth / 4f
+                    )
                 }
-                // measure an approximate max intrinsic width
-            }
 
-            pair.first.layout(ParagraphConstraints(pair.second))
+                paragraph(
+                    text = textAndWidth.first.text,
+                    textStyles = textAndWidth.first.textStyles,
+                    constraints = ParagraphConstraints(textAndWidth.second)
+                )
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/ui/settings.gradle b/ui/settings.gradle
index 674d321..a87bf75 100644
--- a/ui/settings.gradle
+++ b/ui/settings.gradle
@@ -31,6 +31,7 @@
 includeProject(":compose:compose-ide-plugin", "../compose/compose-ide-plugin")
 includeProject(":compose:compose-runtime", "../compose/compose-runtime")
 includeProject(":compose:compose-runtime-benchmark", "../compose/compose-runtime/compose-runtime-benchmark")
+includeProject(":compose:compose-runtime:integration-tests:samples", "../compose/compose-runtime/integration-tests/samples")
 includeProject(":ui:integration-tests-benchmark", "integration-tests/benchmark")
 includeProject(":ui:integration-tests:demos", "integration-tests/demos")
 includeProject(":ui:integration-tests:test", "integration-tests/test")
diff --git a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/AnimatableSeekBar.kt b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/AnimatableSeekBar.kt
index 3bbc4cb..9dbb5e0 100644
--- a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/AnimatableSeekBar.kt
+++ b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/AnimatableSeekBar.kt
@@ -84,9 +84,10 @@
 
     @Composable
     fun DrawSeekBar(x: Float) {
-        var paint = +memo { Paint() }
+        val paint = +memo { Paint() }
         Draw { canvas, parentSize ->
             val centerY = parentSize.height.value / 2
+            val xConstraint = x.coerceIn(0f, parentSize.width.value)
             // draw bar
             paint.color = Color.Gray
             canvas.drawRect(
@@ -95,12 +96,13 @@
             )
             paint.color = Color.Magenta
             canvas.drawRect(
-                Rect(0f, centerY - 5, x, centerY + 5),
+                Rect(0f, centerY - 5, xConstraint, centerY + 5),
                 paint
             )
+
             // draw ticker
             canvas.drawCircle(
-                Offset(x, centerY), 40f, paint
+                Offset(xConstraint, centerY), 40f, paint
             )
         }
     }
diff --git a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SwipeToDismiss.kt b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SwipeToDismiss.kt
index 873687d..1414bfc 100644
--- a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SwipeToDismiss.kt
+++ b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SwipeToDismiss.kt
@@ -75,7 +75,7 @@
         val itemWidth = +state { 0f }
         val isFlinging = +state { false }
         RawDragGestureDetector(dragObserver = object : DragObserver {
-            override fun onStart() {
+            override fun onStart(downPosition: PxPosition) {
                 itemBottom.setBounds(0f, height)
                 if (isFlinging.value && itemBottom.targetValue < 100f) {
                     reset()
diff --git a/ui/ui-foundation/api/0.1.0-dev01.txt b/ui/ui-foundation/api/0.1.0-dev01.txt
index a22542a..06feb29 100644
--- a/ui/ui-foundation/api/0.1.0-dev01.txt
+++ b/ui/ui-foundation/api/0.1.0-dev01.txt
@@ -207,8 +207,9 @@
 
   public final class ToggleableKt {
     ctor public ToggleableKt();
-    method public static void Toggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Toggleable(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.ui.foundation.selection.ToggleableState ToggleableState(boolean checked);
+    method public static void TriStateToggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public enum ToggleableState {
diff --git a/ui/ui-foundation/api/current.txt b/ui/ui-foundation/api/current.txt
index a22542a..06feb29 100644
--- a/ui/ui-foundation/api/current.txt
+++ b/ui/ui-foundation/api/current.txt
@@ -207,8 +207,9 @@
 
   public final class ToggleableKt {
     ctor public ToggleableKt();
-    method public static void Toggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Toggleable(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.ui.foundation.selection.ToggleableState ToggleableState(boolean checked);
+    method public static void TriStateToggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public enum ToggleableState {
diff --git a/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev01.txt b/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev01.txt
index a22542a..06feb29 100644
--- a/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev01.txt
+++ b/ui/ui-foundation/api/public_plus_experimental_0.1.0-dev01.txt
@@ -207,8 +207,9 @@
 
   public final class ToggleableKt {
     ctor public ToggleableKt();
-    method public static void Toggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Toggleable(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.ui.foundation.selection.ToggleableState ToggleableState(boolean checked);
+    method public static void TriStateToggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public enum ToggleableState {
diff --git a/ui/ui-foundation/api/public_plus_experimental_current.txt b/ui/ui-foundation/api/public_plus_experimental_current.txt
index a22542a..06feb29 100644
--- a/ui/ui-foundation/api/public_plus_experimental_current.txt
+++ b/ui/ui-foundation/api/public_plus_experimental_current.txt
@@ -207,8 +207,9 @@
 
   public final class ToggleableKt {
     ctor public ToggleableKt();
-    method public static void Toggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Toggleable(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.ui.foundation.selection.ToggleableState ToggleableState(boolean checked);
+    method public static void TriStateToggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public enum ToggleableState {
diff --git a/ui/ui-foundation/api/restricted_0.1.0-dev01.txt b/ui/ui-foundation/api/restricted_0.1.0-dev01.txt
index a22542a..06feb29 100644
--- a/ui/ui-foundation/api/restricted_0.1.0-dev01.txt
+++ b/ui/ui-foundation/api/restricted_0.1.0-dev01.txt
@@ -207,8 +207,9 @@
 
   public final class ToggleableKt {
     ctor public ToggleableKt();
-    method public static void Toggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Toggleable(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.ui.foundation.selection.ToggleableState ToggleableState(boolean checked);
+    method public static void TriStateToggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public enum ToggleableState {
diff --git a/ui/ui-foundation/api/restricted_current.txt b/ui/ui-foundation/api/restricted_current.txt
index a22542a..06feb29 100644
--- a/ui/ui-foundation/api/restricted_current.txt
+++ b/ui/ui-foundation/api/restricted_current.txt
@@ -207,8 +207,9 @@
 
   public final class ToggleableKt {
     ctor public ToggleableKt();
-    method public static void Toggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void Toggleable(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.ui.foundation.selection.ToggleableState ToggleableState(boolean checked);
+    method public static void TriStateToggleable(androidx.ui.foundation.selection.ToggleableState value = ToggleableState.Checked, kotlin.jvm.functions.Function0<kotlin.Unit>? onToggle = null, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
 
   public enum ToggleableState {
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ToggleableSamples.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ToggleableSamples.kt
new file mode 100644
index 0000000..732be7f
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ToggleableSamples.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.foundation.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.Text
+import androidx.ui.foundation.selection.Toggleable
+import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.foundation.selection.TriStateToggleable
+
+@Sampled
+@Composable
+fun ToggleableSample() {
+    var checked by +state { false }
+    Toggleable(checked = checked, onCheckedChange = { checked = it }) {
+        // content that you want to make toggleable
+        Text(text = checked.toString())
+    }
+}
+
+@Sampled
+@Composable
+fun TriStateToggleableSample() {
+    var checked by +state { ToggleableState.Indeterminate }
+    TriStateToggleable(
+        value = checked,
+        onToggle = {
+            checked =
+                if (checked == ToggleableState.Checked) {
+                    ToggleableState.Unchecked
+                } else {
+                    ToggleableState.Checked
+                }
+        }) {
+        // content that you want to make toggleable
+        Text(text = checked.toString())
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ToggleableTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ToggleableTest.kt
new file mode 100644
index 0000000..8df88c7
--- /dev/null
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ToggleableTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.foundation
+
+import androidx.compose.composer
+import androidx.test.filters.MediumTest
+import androidx.ui.core.TestTag
+import androidx.ui.core.Text
+import androidx.ui.foundation.selection.Toggleable
+import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.foundation.selection.TriStateToggleable
+import androidx.ui.layout.Center
+import androidx.ui.layout.Column
+import androidx.ui.test.assertHasClickAction
+import androidx.ui.test.assertHasNoClickAction
+import androidx.ui.test.assertSemanticsIsEqualTo
+import androidx.ui.test.createComposeRule
+import androidx.ui.test.createFullSemantics
+import androidx.ui.test.doClick
+import androidx.ui.test.findByTag
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@MediumTest
+@RunWith(JUnit4::class)
+class ToggleableTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun toggleableTest_defaultSemantics() {
+        composeTestRule.setContent {
+            Column {
+                TestTag(tag = "checkedToggleable") {
+                    TriStateToggleable(ToggleableState.Checked, onToggle = {}) {
+                        Text("ToggleableText")
+                    }
+                }
+                TestTag(tag = "unCheckedToggleable") {
+                    TriStateToggleable(ToggleableState.Unchecked, onToggle = {}) {
+                        Text("ToggleableText")
+                    }
+                }
+                TestTag(tag = "indeterminateToggleable") {
+                    TriStateToggleable(ToggleableState.Indeterminate, onToggle = {}) {
+                        Text("ToggleableText")
+                    }
+                }
+            }
+        }
+
+        findByTag("checkedToggleable")
+            .assertSemanticsIsEqualTo(
+                createFullSemantics(
+                    isEnabled = true,
+                    toggleableState = ToggleableState.Checked
+                )
+            )
+            .assertHasClickAction()
+        findByTag("unCheckedToggleable")
+            .assertSemanticsIsEqualTo(
+                createFullSemantics(
+                    isEnabled = true,
+                    toggleableState = ToggleableState.Unchecked
+                )
+            )
+            .assertHasClickAction()
+        findByTag("indeterminateToggleable")
+            .assertSemanticsIsEqualTo(
+                createFullSemantics(
+                    isEnabled = true,
+                    toggleableState = ToggleableState.Indeterminate
+                )
+            )
+            .assertHasClickAction()
+    }
+
+    @Test
+    fun toggleableTest_booleanOverload_defaultSemantics() {
+        composeTestRule.setContent {
+            Column {
+                TestTag(tag = "checkedToggleable") {
+                    Toggleable(checked = true, onCheckedChange = {}) {
+                        Text("ToggleableText")
+                    }
+                }
+                TestTag(tag = "unCheckedToggleable") {
+                    Toggleable(checked = false, onCheckedChange = {}) {
+                        Text("ToggleableText")
+                    }
+                }
+            }
+        }
+
+        findByTag("checkedToggleable")
+            .assertSemanticsIsEqualTo(
+                createFullSemantics(
+                    isEnabled = true,
+                    toggleableState = ToggleableState.Checked
+                )
+            )
+            .assertHasClickAction()
+        findByTag("unCheckedToggleable")
+            .assertSemanticsIsEqualTo(
+                createFullSemantics(
+                    isEnabled = true,
+                    toggleableState = ToggleableState.Unchecked
+                )
+            )
+            .assertHasClickAction()
+    }
+
+    @Test
+    fun toggleableTest_disabledSemantics() {
+        composeTestRule.setContent {
+            Center {
+                TestTag(tag = "myToggleable") {
+                    TriStateToggleable(value = ToggleableState.Checked) {
+                        Text("ToggleableText")
+                    }
+                }
+            }
+        }
+
+        findByTag("myToggleable")
+            .assertSemanticsIsEqualTo(
+                createFullSemantics(
+                    isEnabled = false
+                )
+            )
+            .assertHasNoClickAction()
+    }
+
+    @Test
+    fun toggleableTest_toggle() {
+        var checked = true
+        val onCheckedChange: (Boolean) -> Unit = { checked = it }
+
+        composeTestRule.setContent {
+            Center {
+                TestTag(tag = "myToggleable") {
+                    Toggleable(checked = checked, onCheckedChange = onCheckedChange) {
+                        Text("ToggleableText")
+                    }
+                }
+            }
+        }
+
+        findByTag("myToggleable")
+            .doClick()
+
+        Truth
+            .assertThat(checked)
+            .isEqualTo(false)
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt
index ae08bf0..fb12c74 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/selection/Toggleable.kt
@@ -26,8 +26,50 @@
 import androidx.ui.semantics.onClick
 import androidx.ui.semantics.accessibilityValue
 
+/**
+ * Combines [PressReleasedGestureDetector] and [Semantics] for the components that need to be
+ * toggleable, like Switch.
+ *
+ * @sample androidx.ui.foundation.samples.ToggleableSample
+ *
+ * @see [TriStateToggleable] if you require support for an indeterminate state.
+ *
+ * @param checked whether Toggleable is checked or unchecked
+ * @param onCheckedChange callback to be invoked when toggleable is being clicked,
+ * therefore the change of checked state in requested.
+ * If null, Toggleable will appears in the [checked] state and remains disabled
+ */
 @Composable
 fun Toggleable(
+    checked: Boolean,
+    onCheckedChange: ((Boolean) -> Unit)? = null,
+    children: @Composable() () -> Unit
+) {
+    TriStateToggleable(
+        value = ToggleableState(checked),
+        onToggle = onCheckedChange?.let { { it(!checked) } },
+        children = children
+    )
+}
+/**
+ * Combines [PressReleasedGestureDetector] and [Semantics] for the components with three states
+ * like Checkbox.
+ *
+ * It supports three states: checked, unchecked and indeterminate.
+ *
+ * TriStateToggleable should be used when there are
+ * dependent Toggleables associated to this component and those can have different values.
+ *
+ * @sample androidx.ui.foundation.samples.TriStateToggleableSample
+ *
+ * @see [Toggleable] if you want to support only two states: checked and unchecked
+ *
+ * @param value current value for the component
+ * @param onToggle will be called when user toggles the toggleable. The children will not be
+ *  toggleable when it is null.
+ */
+@Composable
+fun TriStateToggleable(
     value: ToggleableState = ToggleableState.Checked,
     onToggle: (() -> Unit)? = null,
     children: @Composable() () -> Unit
@@ -36,7 +78,6 @@
         onRelease = onToggle,
         consumeDownOnStart = false
     ) {
-        // TODO: enabled should not be hardcoded
         // TODO(pavlis): Handle multiple states for Semantics
         Semantics(properties = {
             this.accessibilityValue = when (value) {
@@ -46,7 +87,7 @@
                 ToggleableState.Indeterminate -> Strings.Indeterminate
             }
             this.toggleableState = value
-            this.enabled = true
+            this.enabled = onToggle != null
 
             if (onToggle != null) {
                 onClick(action = onToggle, label = "Toggle")
diff --git a/ui/ui-framework/api/0.1.0-dev01.txt b/ui/ui-framework/api/0.1.0-dev01.txt
index 0f938ce..448df1d 100644
--- a/ui/ui-framework/api/0.1.0-dev01.txt
+++ b/ui/ui-framework/api/0.1.0-dev01.txt
@@ -174,7 +174,7 @@
 
   public interface DragObserver {
     method public default androidx.ui.core.PxPosition onDrag(androidx.ui.core.PxPosition dragDistance);
-    method public default void onStart();
+    method public default void onStart(androidx.ui.core.PxPosition downPosition);
     method public default void onStop(androidx.ui.core.PxPosition velocity);
   }
 
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index 0f938ce..448df1d 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -174,7 +174,7 @@
 
   public interface DragObserver {
     method public default androidx.ui.core.PxPosition onDrag(androidx.ui.core.PxPosition dragDistance);
-    method public default void onStart();
+    method public default void onStart(androidx.ui.core.PxPosition downPosition);
     method public default void onStop(androidx.ui.core.PxPosition velocity);
   }
 
diff --git a/ui/ui-framework/api/public_plus_experimental_0.1.0-dev01.txt b/ui/ui-framework/api/public_plus_experimental_0.1.0-dev01.txt
index 0f938ce..448df1d 100644
--- a/ui/ui-framework/api/public_plus_experimental_0.1.0-dev01.txt
+++ b/ui/ui-framework/api/public_plus_experimental_0.1.0-dev01.txt
@@ -174,7 +174,7 @@
 
   public interface DragObserver {
     method public default androidx.ui.core.PxPosition onDrag(androidx.ui.core.PxPosition dragDistance);
-    method public default void onStart();
+    method public default void onStart(androidx.ui.core.PxPosition downPosition);
     method public default void onStop(androidx.ui.core.PxPosition velocity);
   }
 
diff --git a/ui/ui-framework/api/public_plus_experimental_current.txt b/ui/ui-framework/api/public_plus_experimental_current.txt
index 0f938ce..448df1d 100644
--- a/ui/ui-framework/api/public_plus_experimental_current.txt
+++ b/ui/ui-framework/api/public_plus_experimental_current.txt
@@ -174,7 +174,7 @@
 
   public interface DragObserver {
     method public default androidx.ui.core.PxPosition onDrag(androidx.ui.core.PxPosition dragDistance);
-    method public default void onStart();
+    method public default void onStart(androidx.ui.core.PxPosition downPosition);
     method public default void onStop(androidx.ui.core.PxPosition velocity);
   }
 
diff --git a/ui/ui-framework/api/restricted_0.1.0-dev01.txt b/ui/ui-framework/api/restricted_0.1.0-dev01.txt
index 0f938ce..448df1d 100644
--- a/ui/ui-framework/api/restricted_0.1.0-dev01.txt
+++ b/ui/ui-framework/api/restricted_0.1.0-dev01.txt
@@ -174,7 +174,7 @@
 
   public interface DragObserver {
     method public default androidx.ui.core.PxPosition onDrag(androidx.ui.core.PxPosition dragDistance);
-    method public default void onStart();
+    method public default void onStart(androidx.ui.core.PxPosition downPosition);
     method public default void onStop(androidx.ui.core.PxPosition velocity);
   }
 
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index 0f938ce..448df1d 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -174,7 +174,7 @@
 
   public interface DragObserver {
     method public default androidx.ui.core.PxPosition onDrag(androidx.ui.core.PxPosition dragDistance);
-    method public default void onStart();
+    method public default void onStart(androidx.ui.core.PxPosition downPosition);
     method public default void onStop(androidx.ui.core.PxPosition velocity);
   }
 
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
index 84a2007..7a6e92d 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
@@ -73,10 +73,9 @@
         ellipsis = false,
         density = density,
         layoutDirection = LayoutDirection.Ltr,
-        resourceLoader = resourceLoader
-    ).apply {
-        layout(ParagraphConstraints(width = Float.POSITIVE_INFINITY))
-    }.height.roundToInt().ipx
+        resourceLoader = resourceLoader,
+        constraints = ParagraphConstraints(width = Float.POSITIVE_INFINITY)
+    ).height.roundToInt().ipx
 }
 
 internal class TextFieldDelegate {
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressDragGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressDragGestureDetector.kt
index 2005536..8bf47bf 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressDragGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressDragGestureDetector.kt
@@ -132,7 +132,7 @@
 
         object : DragObserver {
 
-            override fun onStart() {
+            override fun onStart(downPosition: PxPosition) {
                 longPressDragObserver.onDragStart()
                 dragStarted = true
             }
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawDragGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawDragGestureDetector.kt
index 26a0800..08a20e1 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawDragGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawDragGestureDetector.kt
@@ -43,10 +43,12 @@
      * returns true) and the average distance the pointers have moved
      * are not 0 on both the x and y axes.
      *
+     * @param downPosition The pointer input position of the down event.
+     *
      * @see onDrag
      * @see onStop
      */
-    fun onStart() {}
+    fun onStart(downPosition: PxPosition) {}
 
     /**
      * Override to be notified when a distance has been dragged.
@@ -126,6 +128,7 @@
 
 internal class RawDragGestureRecognizer {
     private val velocityTrackers: MutableMap<Int, VelocityTracker> = mutableMapOf()
+    private val downPositions: MutableMap<Int, PxPosition> = mutableMapOf()
     private var started = false
     var canStartDragging: (() -> Boolean)? = null
     lateinit var dragObserver: DragObserver
@@ -167,6 +170,10 @@
                         } else if (it.changedToUpIgnoreConsumed()) {
                             velocityTrackers.remove(it.id)
                         }
+                        // removing stored down position for the pointer.
+                        if (it.changedToUp()) {
+                            downPositions.remove(it.id)
+                        }
                     }
 
                     if (changesToReturn.all { it.changedToUpIgnoreConsumed() }) {
@@ -202,6 +209,7 @@
                                         it.current.position!!
                                     )
                                 }
+                            downPositions[it.id] = it.current.position!!
                         }
                     }
                 }
@@ -258,7 +266,8 @@
                         // and if we should, update our state and call onStart().
                         if (!started && canStart) {
                             started = true
-                            dragObserver.onStart()
+                            dragObserver.onStart(downPositions.values.averagePosition())
+                            downPositions.clear()
                         }
 
                         if (started) {
@@ -282,4 +291,14 @@
 
             changesToReturn
         }
-}
\ No newline at end of file
+}
+
+private fun Iterable<PxPosition>.averagePosition(): PxPosition {
+    var x = 0.px
+    var y = 0.px
+    forEach {
+        x += it.x
+        y += it.y
+    }
+    return PxPosition(x / count(), y / count())
+}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt
index ca647a7..636c602 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt
@@ -21,7 +21,6 @@
 import androidx.compose.unaryPlus
 import androidx.ui.core.Direction
 import androidx.ui.core.PxPosition
-import androidx.compose.composer
 
 // TODO(shepshapard): Convert to functional component with effects once effects are ready.
 /**
@@ -73,8 +72,8 @@
 
     val rawDragObserver: DragObserver =
         object : DragObserver {
-            override fun onStart() {
-                touchSlopDragObserver.onStart()
+            override fun onStart(downPosition: PxPosition) {
+                touchSlopDragObserver.onStart(downPosition)
             }
 
             override fun onDrag(dragDistance: PxPosition): PxPosition {
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
index 4af3009..5e8c9a7 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
@@ -114,7 +114,7 @@
 
     fun handleDragObserver(dragStartHandle: Boolean): DragObserver {
         return object : DragObserver {
-            override fun onStart() {
+            override fun onStart(downPosition: PxPosition) {
                 // The LayoutCoordinates of the widget where the drag gesture should begin. This
                 // is used to convert the position of the beginning of the drag gesture from the
                 // widget coordinates to selection container coordinates.
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawDragGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawDragGestureDetectorTest.kt
index ec59cce..bd2e163 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawDragGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawDragGestureDetectorTest.kt
@@ -525,6 +525,40 @@
         assertThat(result.first().consumed.downChange).isTrue()
     }
 
+    @Test
+    fun pointerInputHandler_move_onStartCalledWithDownPosition() {
+        val down = down(x = 3f, y = 4f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(down)
+        dragStartBlocked = false
+
+        val move = down.moveBy(Duration(milliseconds = 10), 1f, 0f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(move)
+
+        assertThat(log.first { it.methodName == "onStart" }.pxPosition)
+            .isEqualTo(PxPosition(3.px, 4.px))
+    }
+
+    @Test
+    fun pointerInputHandler_3PointsMove_onStartCalledWithDownPositions() {
+        var pointer1 = down(id = 1, x = 1f, y = 2f)
+        var pointer2 = down(id = 2, x = 5f, y = 4f)
+        var pointer3 = down(id = 3, x = 3f, y = 6f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2, pointer3)
+        dragStartBlocked = false
+
+        pointer1 = pointer1.moveBy(100.milliseconds, 1f, 0f)
+        pointer2 = pointer2.moveBy(100.milliseconds, 0f, 0f)
+        pointer3 = pointer3.moveBy(100.milliseconds, 0f, 0f)
+
+        // Act
+
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2, pointer3)
+
+        assertThat(log.first { it.methodName == "onStart" }.pxPosition)
+            // average position
+            .isEqualTo(PxPosition(3.px, 4.px))
+    }
+
     data class LogItem(
         val methodName: String,
         val direction: Direction? = null,
@@ -536,9 +570,9 @@
         var dragConsume: PxPosition? = null
     ) : DragObserver {
 
-        override fun onStart() {
-            log.add(LogItem("onStart"))
-            super.onStart()
+        override fun onStart(downPosition: PxPosition) {
+            log.add(LogItem("onStart", pxPosition = downPosition))
+            super.onStart(downPosition)
         }
 
         override fun onDrag(dragDistance: PxPosition): PxPosition {
diff --git a/ui/ui-material/api/0.1.0-dev01.txt b/ui/ui-material/api/0.1.0-dev01.txt
index ff52462..cd753df 100644
--- a/ui/ui-material/api/0.1.0-dev01.txt
+++ b/ui/ui-material/api/0.1.0-dev01.txt
@@ -178,7 +178,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
     method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -255,10 +255,12 @@
   }
 
   public final class Shapes {
-    ctor public Shapes(androidx.ui.engine.geometry.Shape button);
+    ctor public Shapes(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape component1();
-    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button);
+    method public androidx.ui.engine.geometry.Shape component2();
+    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape getButton();
+    method public androidx.ui.engine.geometry.Shape getCard();
   }
 
   public final class SwitchKt {
@@ -374,7 +376,9 @@
 
   public final class CardKt {
     ctor public CardKt();
-    method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void Card(androidx.ui.engine.geometry.Shape shape = +themeShape({ 
+    card
+}), androidx.ui.graphics.Color color = +themeColor({ 
     surface
 }), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index ff52462..cd753df 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -178,7 +178,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
     method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -255,10 +255,12 @@
   }
 
   public final class Shapes {
-    ctor public Shapes(androidx.ui.engine.geometry.Shape button);
+    ctor public Shapes(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape component1();
-    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button);
+    method public androidx.ui.engine.geometry.Shape component2();
+    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape getButton();
+    method public androidx.ui.engine.geometry.Shape getCard();
   }
 
   public final class SwitchKt {
@@ -374,7 +376,9 @@
 
   public final class CardKt {
     ctor public CardKt();
-    method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void Card(androidx.ui.engine.geometry.Shape shape = +themeShape({ 
+    card
+}), androidx.ui.graphics.Color color = +themeColor({ 
     surface
 }), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
diff --git a/ui/ui-material/api/public_plus_experimental_0.1.0-dev01.txt b/ui/ui-material/api/public_plus_experimental_0.1.0-dev01.txt
index ff52462..cd753df 100644
--- a/ui/ui-material/api/public_plus_experimental_0.1.0-dev01.txt
+++ b/ui/ui-material/api/public_plus_experimental_0.1.0-dev01.txt
@@ -178,7 +178,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
     method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -255,10 +255,12 @@
   }
 
   public final class Shapes {
-    ctor public Shapes(androidx.ui.engine.geometry.Shape button);
+    ctor public Shapes(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape component1();
-    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button);
+    method public androidx.ui.engine.geometry.Shape component2();
+    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape getButton();
+    method public androidx.ui.engine.geometry.Shape getCard();
   }
 
   public final class SwitchKt {
@@ -374,7 +376,9 @@
 
   public final class CardKt {
     ctor public CardKt();
-    method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void Card(androidx.ui.engine.geometry.Shape shape = +themeShape({ 
+    card
+}), androidx.ui.graphics.Color color = +themeColor({ 
     surface
 }), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
diff --git a/ui/ui-material/api/public_plus_experimental_current.txt b/ui/ui-material/api/public_plus_experimental_current.txt
index ff52462..cd753df 100644
--- a/ui/ui-material/api/public_plus_experimental_current.txt
+++ b/ui/ui-material/api/public_plus_experimental_current.txt
@@ -178,7 +178,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
     method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -255,10 +255,12 @@
   }
 
   public final class Shapes {
-    ctor public Shapes(androidx.ui.engine.geometry.Shape button);
+    ctor public Shapes(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape component1();
-    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button);
+    method public androidx.ui.engine.geometry.Shape component2();
+    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape getButton();
+    method public androidx.ui.engine.geometry.Shape getCard();
   }
 
   public final class SwitchKt {
@@ -374,7 +376,9 @@
 
   public final class CardKt {
     ctor public CardKt();
-    method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void Card(androidx.ui.engine.geometry.Shape shape = +themeShape({ 
+    card
+}), androidx.ui.graphics.Color color = +themeColor({ 
     surface
 }), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
diff --git a/ui/ui-material/api/restricted_0.1.0-dev01.txt b/ui/ui-material/api/restricted_0.1.0-dev01.txt
index ff52462..cd753df 100644
--- a/ui/ui-material/api/restricted_0.1.0-dev01.txt
+++ b/ui/ui-material/api/restricted_0.1.0-dev01.txt
@@ -178,7 +178,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
     method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -255,10 +255,12 @@
   }
 
   public final class Shapes {
-    ctor public Shapes(androidx.ui.engine.geometry.Shape button);
+    ctor public Shapes(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape component1();
-    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button);
+    method public androidx.ui.engine.geometry.Shape component2();
+    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape getButton();
+    method public androidx.ui.engine.geometry.Shape getCard();
   }
 
   public final class SwitchKt {
@@ -374,7 +376,9 @@
 
   public final class CardKt {
     ctor public CardKt();
-    method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void Card(androidx.ui.engine.geometry.Shape shape = +themeShape({ 
+    card
+}), androidx.ui.graphics.Color color = +themeColor({ 
     surface
 }), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index ff52462..cd753df 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -178,7 +178,7 @@
 
   public final class MaterialThemeKt {
     ctor public MaterialThemeKt();
-    method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void MaterialShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
     method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -255,10 +255,12 @@
   }
 
   public final class Shapes {
-    ctor public Shapes(androidx.ui.engine.geometry.Shape button);
+    ctor public Shapes(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape component1();
-    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button);
+    method public androidx.ui.engine.geometry.Shape component2();
+    method public androidx.ui.material.Shapes copy(androidx.ui.engine.geometry.Shape button, androidx.ui.engine.geometry.Shape card);
     method public androidx.ui.engine.geometry.Shape getButton();
+    method public androidx.ui.engine.geometry.Shape getCard();
   }
 
   public final class SwitchKt {
@@ -374,7 +376,9 @@
 
   public final class CardKt {
     ctor public CardKt();
-    method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({ 
+    method public static void Card(androidx.ui.engine.geometry.Shape shape = +themeShape({ 
+    card
+}), androidx.ui.graphics.Color color = +themeColor({ 
     surface
 }), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
   }
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt
index 4bd7229..1797a21 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt
@@ -65,10 +65,10 @@
         composeTestRule.setMaterialContent {
             Column {
                 TestTag(tag = "checked") {
-                    Switch(checked = true, onCheckedChange = null)
+                    Switch(checked = true, onCheckedChange = {})
                 }
                 TestTag(tag = "unchecked") {
-                    Switch(checked = false, onCheckedChange = null)
+                    Switch(checked = false, onCheckedChange = {})
                 }
             }
         }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt b/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt
index 04ecdba..eb0c20e 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Checkbox.kt
@@ -32,8 +32,8 @@
 import androidx.ui.engine.geometry.Radius
 import androidx.ui.engine.geometry.shrink
 import androidx.ui.engine.geometry.withRadius
-import androidx.ui.foundation.selection.Toggleable
 import androidx.ui.foundation.selection.ToggleableState
+import androidx.ui.foundation.selection.TriStateToggleable
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Container
 import androidx.ui.layout.Padding
@@ -92,7 +92,7 @@
 ) {
     Wrap {
         Ripple(bounded = false) {
-            Toggleable(value = value, onToggle = onClick) {
+            TriStateToggleable(value = value, onToggle = onClick) {
                 Padding(padding = CheckboxDefaultPadding) {
                     Container(width = CheckboxSize, height = CheckboxSize) {
                         DrawCheckbox(value = value, activeColor = color)
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
index 87ddd92..6e6457a 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
@@ -29,6 +29,7 @@
 import androidx.ui.core.sp
 import androidx.ui.core.withDensity
 import androidx.ui.engine.geometry.Shape
+import androidx.ui.foundation.shape.RectangleShape
 import androidx.ui.foundation.shape.corner.RoundedCornerShape
 import androidx.ui.graphics.Color
 import androidx.ui.graphics.luminance
@@ -67,7 +68,7 @@
         Typography.Provider(value = typography) {
             CurrentTextStyleProvider(value = typography.body1) {
                 MaterialRippleTheme {
-                    MaterialButtonShapeTheme(children = children)
+                    MaterialShapeTheme(children = children)
                 }
             }
         }
@@ -280,14 +281,18 @@
     /**
      * Shape used for [Button]
      */
-    val button: Shape
-    // TODO(Andrey): Add shapes for Card, other surfaces? will see what we need.
+    val button: Shape,
+    /**
+     * Shape used for [Card]
+     */
+    val card: Shape
+    // TODO(Andrey): Add shapes for other surfaces? will see what we need.
 )
 
 /**
  * Ambient used to specify the default shapes for the surfaces.
  *
- * @see [MaterialButtonShapeTheme] for the default Material Design value
+ * @see [MaterialShapeTheme] for the default Material Design value
  */
 val CurrentShapeAmbient = Ambient.of<Shapes> {
     throw IllegalStateException("No default shapes provided.")
@@ -297,10 +302,11 @@
  * Applies the default [Shape]s for all the surfaces.
  */
 @Composable
-fun MaterialButtonShapeTheme(children: @Composable() () -> Unit) {
+fun MaterialShapeTheme(children: @Composable() () -> Unit) {
     val value = +withDensity {
         Shapes(
-            button = RoundedCornerShape(4.dp)
+            button = RoundedCornerShape(4.dp),
+            card = RectangleShape
         )
     }
     CurrentShapeAmbient.Provider(value = value, children = children)
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt b/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
index 0eda4ed..099b07e 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/RadioButton.kt
@@ -36,8 +36,6 @@
 import androidx.ui.engine.geometry.shift
 import androidx.ui.engine.geometry.shrink
 import androidx.ui.foundation.selection.MutuallyExclusiveSetItem
-import androidx.ui.foundation.selection.Toggleable
-import androidx.ui.foundation.selection.ToggleableState
 import androidx.ui.graphics.Color
 import androidx.ui.layout.Column
 import androidx.ui.layout.Container
@@ -211,9 +209,8 @@
 ) {
     Wrap {
         Ripple(bounded = false) {
-            Toggleable(
-                value = if (selected) ToggleableState.Checked else ToggleableState.Unchecked,
-                onToggle = onSelect
+            MutuallyExclusiveSetItem(
+                selected = selected, onClick = { if (!selected) onSelect?.invoke() }
             ) {
                 Padding(padding = RadioButtonPadding) {
                     Container(width = RadioButtonSize, height = RadioButtonSize) {
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
index a0ca195..21368a8 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Switch.kt
@@ -57,10 +57,9 @@
     onCheckedChange: ((Boolean) -> Unit)?,
     color: Color = +themeColor { secondaryVariant }
 ) {
-    val value = if (checked) ToggleableState.Checked else ToggleableState.Unchecked
     Wrap {
         Ripple(bounded = false) {
-            Toggleable(value = value, onToggle = onCheckedChange?.let { { it(!checked) } }) {
+            Toggleable(checked = checked, onCheckedChange = onCheckedChange) {
                 Padding(padding = DefaultSwitchPadding) {
                     SwitchImpl(checked, onCheckedChange, color)
                 }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
index 8ec7d6a..65aaf11 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
@@ -22,11 +22,11 @@
 import androidx.ui.core.Dp
 import androidx.ui.core.dp
 import androidx.ui.engine.geometry.Shape
-import androidx.ui.foundation.shape.RectangleShape
 import androidx.ui.foundation.shape.border.Border
 import androidx.ui.graphics.Color
 import androidx.ui.material.MaterialColors
 import androidx.ui.material.themeColor
+import androidx.ui.material.themeShape
 
 /**
  * Cards are [Surface]s that display content and actions on a single topic.
@@ -44,7 +44,7 @@
  */
 @Composable
 fun Card(
-    shape: Shape = RectangleShape, // TODO (Andrey: Take the default shape from the theme)
+    shape: Shape = +themeShape { card },
     color: Color = +themeColor { surface },
     border: Border? = null,
     elevation: Dp = 1.dp,
diff --git a/ui/ui-text/api/0.1.0-dev01.txt b/ui/ui-text/api/0.1.0-dev01.txt
index 13ac155..0c17e79a 100644
--- a/ui/ui-text/api/0.1.0-dev01.txt
+++ b/ui/ui-text/api/0.1.0-dev01.txt
@@ -156,6 +156,7 @@
     method public float getFirstBaseline();
     method public float getHeight();
     method public float getLastBaseline();
+    method public float getLineBottom(int lineIndex);
     method public int getLineCount();
     method public float getLineHeight(int lineIndex);
     method public float getLineLeft(int lineIndex);
@@ -168,7 +169,6 @@
     method public androidx.ui.graphics.Path getPathForRange(int start, int end);
     method public float getWidth();
     method public androidx.ui.text.TextRange getWordBoundary(int offset);
-    method public void layout(androidx.ui.text.ParagraphConstraints constraints);
     method public void paint(androidx.ui.graphics.Canvas canvas);
     property public abstract boolean didExceedMaxLines;
     property public abstract float firstBaseline;
@@ -201,8 +201,8 @@
 
   public final class ParagraphKt {
     ctor public ParagraphKt();
-    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
-    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null);
+    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
+    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints);
   }
 
   public final class ParagraphStyle {
diff --git a/ui/ui-text/api/api_lint.ignore b/ui/ui-text/api/api_lint.ignore
index 075c474..dc9f989 100644
--- a/ui/ui-text/api/api_lint.ignore
+++ b/ui/ui-text/api/api_lint.ignore
@@ -1,7 +1,7 @@
 // Baseline format: 1.0
-AutoBoxing: androidx.ui.text.ParagraphKt#Paragraph(String, androidx.ui.text.TextStyle, androidx.ui.text.ParagraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>>, Integer, Boolean, androidx.ui.core.Density, androidx.ui.core.LayoutDirection, androidx.ui.text.font.Font.ResourceLoader) parameter #4:
+AutoBoxing: androidx.ui.text.ParagraphKt#Paragraph(String, androidx.ui.text.TextStyle, androidx.ui.text.ParagraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>>, Integer, Boolean, androidx.ui.text.ParagraphConstraints, androidx.ui.core.Density, androidx.ui.core.LayoutDirection, androidx.ui.text.font.Font.ResourceLoader) parameter #4:
     Must avoid boxed primitives (`java.lang.Integer`)
-AutoBoxing: androidx.ui.text.ParagraphKt#Paragraph(androidx.ui.text.ParagraphIntrinsics, Integer, Boolean) parameter #1:
+AutoBoxing: androidx.ui.text.ParagraphKt#Paragraph(androidx.ui.text.ParagraphIntrinsics, Integer, Boolean, androidx.ui.text.ParagraphConstraints) parameter #1:
     Must avoid boxed primitives (`java.lang.Integer`)
 AutoBoxing: androidx.ui.text.ParagraphStyle#ParagraphStyle(androidx.ui.text.style.TextAlign, androidx.ui.text.style.TextDirectionAlgorithm, Float, androidx.ui.text.style.TextIndent) parameter #2:
     Must avoid boxed primitives (`java.lang.Float`)
diff --git a/ui/ui-text/api/current.txt b/ui/ui-text/api/current.txt
index 13ac155..0c17e79a 100644
--- a/ui/ui-text/api/current.txt
+++ b/ui/ui-text/api/current.txt
@@ -156,6 +156,7 @@
     method public float getFirstBaseline();
     method public float getHeight();
     method public float getLastBaseline();
+    method public float getLineBottom(int lineIndex);
     method public int getLineCount();
     method public float getLineHeight(int lineIndex);
     method public float getLineLeft(int lineIndex);
@@ -168,7 +169,6 @@
     method public androidx.ui.graphics.Path getPathForRange(int start, int end);
     method public float getWidth();
     method public androidx.ui.text.TextRange getWordBoundary(int offset);
-    method public void layout(androidx.ui.text.ParagraphConstraints constraints);
     method public void paint(androidx.ui.graphics.Canvas canvas);
     property public abstract boolean didExceedMaxLines;
     property public abstract float firstBaseline;
@@ -201,8 +201,8 @@
 
   public final class ParagraphKt {
     ctor public ParagraphKt();
-    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
-    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null);
+    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
+    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints);
   }
 
   public final class ParagraphStyle {
diff --git a/ui/ui-text/api/public_plus_experimental_0.1.0-dev01.txt b/ui/ui-text/api/public_plus_experimental_0.1.0-dev01.txt
index 13ac155..0c17e79a 100644
--- a/ui/ui-text/api/public_plus_experimental_0.1.0-dev01.txt
+++ b/ui/ui-text/api/public_plus_experimental_0.1.0-dev01.txt
@@ -156,6 +156,7 @@
     method public float getFirstBaseline();
     method public float getHeight();
     method public float getLastBaseline();
+    method public float getLineBottom(int lineIndex);
     method public int getLineCount();
     method public float getLineHeight(int lineIndex);
     method public float getLineLeft(int lineIndex);
@@ -168,7 +169,6 @@
     method public androidx.ui.graphics.Path getPathForRange(int start, int end);
     method public float getWidth();
     method public androidx.ui.text.TextRange getWordBoundary(int offset);
-    method public void layout(androidx.ui.text.ParagraphConstraints constraints);
     method public void paint(androidx.ui.graphics.Canvas canvas);
     property public abstract boolean didExceedMaxLines;
     property public abstract float firstBaseline;
@@ -201,8 +201,8 @@
 
   public final class ParagraphKt {
     ctor public ParagraphKt();
-    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
-    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null);
+    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
+    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints);
   }
 
   public final class ParagraphStyle {
diff --git a/ui/ui-text/api/public_plus_experimental_current.txt b/ui/ui-text/api/public_plus_experimental_current.txt
index 13ac155..0c17e79a 100644
--- a/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/ui/ui-text/api/public_plus_experimental_current.txt
@@ -156,6 +156,7 @@
     method public float getFirstBaseline();
     method public float getHeight();
     method public float getLastBaseline();
+    method public float getLineBottom(int lineIndex);
     method public int getLineCount();
     method public float getLineHeight(int lineIndex);
     method public float getLineLeft(int lineIndex);
@@ -168,7 +169,6 @@
     method public androidx.ui.graphics.Path getPathForRange(int start, int end);
     method public float getWidth();
     method public androidx.ui.text.TextRange getWordBoundary(int offset);
-    method public void layout(androidx.ui.text.ParagraphConstraints constraints);
     method public void paint(androidx.ui.graphics.Canvas canvas);
     property public abstract boolean didExceedMaxLines;
     property public abstract float firstBaseline;
@@ -201,8 +201,8 @@
 
   public final class ParagraphKt {
     ctor public ParagraphKt();
-    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
-    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null);
+    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
+    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints);
   }
 
   public final class ParagraphStyle {
diff --git a/ui/ui-text/api/restricted_0.1.0-dev01.txt b/ui/ui-text/api/restricted_0.1.0-dev01.txt
index 3c0634e..944d464 100644
--- a/ui/ui-text/api/restricted_0.1.0-dev01.txt
+++ b/ui/ui-text/api/restricted_0.1.0-dev01.txt
@@ -179,6 +179,7 @@
     method public float getFirstBaseline();
     method public float getHeight();
     method public float getLastBaseline();
+    method public float getLineBottom(int lineIndex);
     method public int getLineCount();
     method public float getLineHeight(int lineIndex);
     method public float getLineLeft(int lineIndex);
@@ -191,7 +192,6 @@
     method public androidx.ui.graphics.Path getPathForRange(int start, int end);
     method public float getWidth();
     method public androidx.ui.text.TextRange getWordBoundary(int offset);
-    method public void layout(androidx.ui.text.ParagraphConstraints constraints);
     method public void paint(androidx.ui.graphics.Canvas canvas);
     property public abstract boolean didExceedMaxLines;
     property public abstract float firstBaseline;
@@ -224,8 +224,8 @@
 
   public final class ParagraphKt {
     ctor public ParagraphKt();
-    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
-    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null);
+    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
+    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints);
   }
 
   public final class ParagraphStyle {
diff --git a/ui/ui-text/api/restricted_current.txt b/ui/ui-text/api/restricted_current.txt
index 3c0634e..944d464 100644
--- a/ui/ui-text/api/restricted_current.txt
+++ b/ui/ui-text/api/restricted_current.txt
@@ -179,6 +179,7 @@
     method public float getFirstBaseline();
     method public float getHeight();
     method public float getLastBaseline();
+    method public float getLineBottom(int lineIndex);
     method public int getLineCount();
     method public float getLineHeight(int lineIndex);
     method public float getLineLeft(int lineIndex);
@@ -191,7 +192,6 @@
     method public androidx.ui.graphics.Path getPathForRange(int start, int end);
     method public float getWidth();
     method public androidx.ui.text.TextRange getWordBoundary(int offset);
-    method public void layout(androidx.ui.text.ParagraphConstraints constraints);
     method public void paint(androidx.ui.graphics.Canvas canvas);
     property public abstract boolean didExceedMaxLines;
     property public abstract float firstBaseline;
@@ -224,8 +224,8 @@
 
   public final class ParagraphKt {
     ctor public ParagraphKt();
-    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
-    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null);
+    method public static androidx.ui.text.Paragraph Paragraph(String text, androidx.ui.text.TextStyle style, androidx.ui.text.ParagraphStyle paragraphStyle, java.util.List<androidx.ui.text.AnnotatedString.Item<androidx.ui.text.TextStyle>> textStyles, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints, androidx.ui.core.Density density, androidx.ui.core.LayoutDirection layoutDirection, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
+    method public static androidx.ui.text.Paragraph Paragraph(androidx.ui.text.ParagraphIntrinsics paragraphIntrinsics, Integer? maxLines = null, Boolean? ellipsis = null, androidx.ui.text.ParagraphConstraints constraints);
   }
 
   public final class ParagraphStyle {
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
index c521776..8c10b94 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
@@ -36,7 +36,6 @@
 import androidx.ui.text.style.TextDirection
 import androidx.ui.text.style.TextDirectionAlgorithm
 import androidx.ui.text.style.TextIndent
-import com.nhaarman.mockitokotlin2.mock
 import org.hamcrest.Matchers.equalTo
 import org.junit.Assert.assertThat
 import org.junit.Test
@@ -57,9 +56,11 @@
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
             val text = ""
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = 100.0f))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = 100.0f)
+            )
 
             assertThat(paragraph.width, equalTo(100.0f))
 
@@ -79,10 +80,12 @@
             val fontSizeInPx = fontSize.toPx().value
 
             for (text in arrayOf("xyz", "\u05D0\u05D1\u05D2")) {
-                val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-                // width greater than text width - 150
-                paragraph.layout(ParagraphConstraints(width = 200.0f))
+                val paragraph = simpleMultiParagraph(
+                    text = text,
+                    fontSize = fontSize,
+                    // width greater than text width - 150
+                    constraints = ParagraphConstraints(width = 200.0f)
+                )
 
                 assertThat(text, paragraph.width, equalTo(200.0f))
                 assertThat(text, paragraph.height, equalTo(fontSizeInPx))
@@ -94,7 +97,8 @@
                     paragraph.maxIntrinsicWidth,
                     equalTo(fontSizeInPx * text.length)
                 )
-                assertThat(text, paragraph.minIntrinsicWidth,
+                assertThat(
+                    text, paragraph.minIntrinsicWidth,
                     equalTo(text.length * fontSizeInPx)
                 )
             }
@@ -108,10 +112,12 @@
             val fontSizeInPx = fontSize.toPx().value
 
             for (text in arrayOf("abcdef", "\u05D0\u05D1\u05D2\u05D3\u05D4\u05D5")) {
-                val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-                // 3 chars width
-                paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+                val paragraph = simpleMultiParagraph(
+                    text = text,
+                    fontSize = fontSize,
+                    // 3 chars width
+                    constraints = ParagraphConstraints(width = 3 * fontSizeInPx)
+                )
 
                 // 3 chars
                 assertThat(text, paragraph.width, equalTo(3 * fontSizeInPx))
@@ -142,9 +148,12 @@
     fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_returnsTrue() {
         val text = "aaa\naa"
         val maxLines = text.lines().size - 1
-        val paragraph = simpleMultiParagraph(text = text, maxLines = maxLines)
+        val paragraph = simpleMultiParagraph(
+            text = text,
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+        )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
         assertThat(paragraph.didExceedMaxLines, equalTo(true))
     }
 
@@ -152,9 +161,12 @@
     fun didExceedMaxLines_withMaxLinesEqualToTextLines_returnsFalse() {
         val text = "aaa\naa"
         val maxLines = text.lines().size
-        val paragraph = simpleMultiParagraph(text = text, maxLines = maxLines)
+        val paragraph = simpleMultiParagraph(
+            text = text,
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+        )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
         assertThat(paragraph.didExceedMaxLines, equalTo(false))
     }
 
@@ -162,9 +174,12 @@
     fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_returnsFalse() {
         val text = "aaa\naa"
         val maxLines = text.lines().size + 1
-        val paragraph = simpleMultiParagraph(text = text, maxLines = maxLines)
+        val paragraph = simpleMultiParagraph(
+            text = text,
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+        )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
         assertThat(paragraph.didExceedMaxLines, equalTo(false))
     }
 
@@ -178,11 +193,11 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                maxLines = maxLines
+                maxLines = maxLines,
+                // One line can only contain 1 character
+                constraints = ParagraphConstraints(width = fontSizeInPx)
             )
 
-            // One line can only contain 1 character
-            paragraph.layout(ParagraphConstraints(width = fontSizeInPx))
             assertThat(paragraph.didExceedMaxLines, equalTo(true))
         }
     }
@@ -191,9 +206,13 @@
     fun didExceedMaxLines_withMaxLinesEqualToTextLines_withLineWrap_returnsFalse() {
         val text = "a"
         val maxLines = text.lines().size
-        val paragraph = simpleMultiParagraph(text = text, fontSize = 50.sp, maxLines = maxLines)
+        val paragraph = simpleMultiParagraph(
+            text = text,
+            fontSize = 50.sp,
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+        )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
         assertThat(paragraph.didExceedMaxLines, equalTo(false))
     }
 
@@ -207,11 +226,11 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                maxLines = maxLines
+                maxLines = maxLines,
+                // One line can only contain 1 character
+                constraints = ParagraphConstraints(width = fontSizeInPx)
             )
 
-            // One line can only contain 1 character
-            paragraph.layout(ParagraphConstraints(width = fontSizeInPx))
             assertThat(paragraph.didExceedMaxLines, equalTo(false))
         }
     }
@@ -222,9 +241,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
             // test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars 0, 1, 2 ...
             for (i in 0..text.length) {
                 val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
@@ -244,9 +266,11 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             // test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars .., 2, 1, 0
             for (i in 0..text.length) {
@@ -269,9 +293,11 @@
             val text = firstLine + secondLine
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = firstLine.length * fontSizeInPx)
+            )
 
             // test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
             // which maps to chars 3, 4, 5
@@ -295,9 +321,11 @@
             val text = firstLine + secondLine
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = firstLine.length * fontSizeInPx)
+            )
 
             // test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
             // which maps to chars 5, 4, 3
@@ -319,9 +347,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             // greater than width
             var position = PxPosition((fontSizeInPx * text.length * 2).px, (fontSizeInPx / 2).px)
@@ -341,9 +371,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             // greater than height
             var position = PxPosition((fontSizeInPx / 2).px, (fontSizeInPx * text.length * 2).px)
@@ -363,9 +395,11 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             assertThat(
                 paragraph.getOffsetForPosition(PxPosition((3 * fontSizeInPx).px, 0.px)),
@@ -390,11 +424,10 @@
                 fontSize = fontSize,
                 paragraphStyles = listOf(
                     AnnotatedString.Item(ParagraphStyle(), 0, 3)
-                )
+                ),
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
             )
 
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
-
             for (i in 0 until 3) {
                 assertThat(
                     paragraph.getOffsetForPosition(PxPosition((i * fontSizeInPx).px, 0.px)),
@@ -419,10 +452,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
-            for (i in 0 until text.length) {
+            for (i in text.indices) {
                 val box = paragraph.getBoundingBox(i)
                 assertThat(box.left, equalTo(i * fontSizeInPx))
                 assertThat(box.right, equalTo((i + 1) * fontSizeInPx))
@@ -440,13 +476,15 @@
             val text = firstLine + secondLine
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = firstLine.length * fontSizeInPx)
+            )
 
             // test positions are 3, 4, 5 and always on the second line
             // which maps to chars 3, 4, 5
-            for (i in 0..secondLine.length - 1) {
+            for (i in secondLine.indices) {
                 val textPosition = i + firstLine.length
                 val box = paragraph.getBoundingBox(textPosition)
                 assertThat(box.left, equalTo(i * fontSizeInPx))
@@ -463,8 +501,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             paragraph.getBoundingBox(-1)
         }
@@ -477,9 +518,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             val textPosition = text.length + 1
             paragraph.getBoundingBox(textPosition)
@@ -492,9 +535,10 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text, fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             paragraph.getCursorRect(text.length + 1)
         }
@@ -506,9 +550,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             paragraph.getCursorRect(-1)
         }
@@ -520,21 +566,25 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
-
-            for (i in 0 until text.length) {
+            for (i in text.indices) {
                 val cursorRect = paragraph.getCursorRect(i)
                 val cursorXOffset = i * fontSizeInPx
                 assertThat(
                     cursorRect,
-                    equalTo(Rect(
-                        left = cursorXOffset - cursorWidth / 2,
-                        top = 0f,
-                        right = cursorXOffset + cursorWidth / 2,
-                        bottom = fontSizeInPx
-                    ))
+                    equalTo(
+                        Rect(
+                            left = cursorXOffset - cursorWidth / 2,
+                            top = 0f,
+                            right = cursorXOffset + cursorWidth / 2,
+                            bottom = fontSizeInPx
+                        )
+                    )
                 )
             }
         }
@@ -546,21 +596,25 @@
             val text = "abcdef"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val charsPerLine = 3
-
-            paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = charsPerLine * fontSizeInPx)
+            )
 
             for (i in 0 until charsPerLine) {
                 val cursorXOffset = i * fontSizeInPx
                 assertThat(
                     paragraph.getCursorRect(i),
-                    equalTo(Rect(
-                        left = cursorXOffset - cursorWidth / 2,
-                        top = 0f,
-                        right = cursorXOffset + cursorWidth / 2,
-                        bottom = fontSizeInPx
-                    ))
+                    equalTo(
+                        Rect(
+                            left = cursorXOffset - cursorWidth / 2,
+                            top = 0f,
+                            right = cursorXOffset + cursorWidth / 2,
+                            bottom = fontSizeInPx
+                        )
+                    )
                 )
             }
 
@@ -568,12 +622,14 @@
                 val cursorXOffset = (i % charsPerLine) * fontSizeInPx
                 assertThat(
                     paragraph.getCursorRect(i),
-                    equalTo(Rect(
-                        left = cursorXOffset - cursorWidth / 2,
-                        top = fontSizeInPx,
-                        right = cursorXOffset + cursorWidth / 2,
-                        bottom = fontSizeInPx * 2.2f
-                    ))
+                    equalTo(
+                        Rect(
+                            left = cursorXOffset - cursorWidth / 2,
+                            top = fontSizeInPx,
+                            right = cursorXOffset + cursorWidth / 2,
+                            bottom = fontSizeInPx * 2.2f
+                        )
+                    )
                 )
             }
         }
@@ -585,20 +641,24 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
-
-            for (i in 0 until text.length) {
+            for (i in text.indices) {
                 val cursorXOffset = (text.length - i) * fontSizeInPx
                 assertThat(
                     paragraph.getCursorRect(i),
-                    equalTo(Rect(
-                        left = cursorXOffset - cursorWidth / 2,
-                        top = 0f,
-                        right = cursorXOffset + cursorWidth / 2,
-                        bottom = fontSizeInPx
-                    ))
+                    equalTo(
+                        Rect(
+                            left = cursorXOffset - cursorWidth / 2,
+                            top = 0f,
+                            right = cursorXOffset + cursorWidth / 2,
+                            bottom = fontSizeInPx
+                        )
+                    )
                 )
             }
         }
@@ -610,21 +670,25 @@
             val text = "\u05D0\u05D1\u05D2\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val charsPerLine = 3
-
-            paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = charsPerLine * fontSizeInPx)
+            )
 
             for (i in 0 until charsPerLine) {
                 val cursorXOffset = (charsPerLine - i) * fontSizeInPx
                 assertThat(
                     paragraph.getCursorRect(i),
-                    equalTo(Rect(
-                        left = cursorXOffset - cursorWidth / 2,
-                        top = 0f,
-                        right = cursorXOffset + cursorWidth / 2,
-                        bottom = fontSizeInPx
-                    ))
+                    equalTo(
+                        Rect(
+                            left = cursorXOffset - cursorWidth / 2,
+                            top = 0f,
+                            right = cursorXOffset + cursorWidth / 2,
+                            bottom = fontSizeInPx
+                        )
+                    )
                 )
             }
 
@@ -632,12 +696,14 @@
                 val cursorXOffset = (charsPerLine - i % charsPerLine) * fontSizeInPx
                 assertThat(
                     paragraph.getCursorRect(i),
-                    equalTo(Rect(
-                        left = cursorXOffset - cursorWidth / 2,
-                        top = fontSizeInPx,
-                        right = cursorXOffset + cursorWidth / 2,
-                        bottom = fontSizeInPx * 2.2f
-                    ))
+                    equalTo(
+                        Rect(
+                            left = cursorXOffset - cursorWidth / 2,
+                            top = fontSizeInPx,
+                            right = cursorXOffset + cursorWidth / 2,
+                            bottom = fontSizeInPx * 2.2f
+                        )
+                    )
                 )
             }
         }
@@ -649,9 +715,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             for (i in 0..text.length) {
                 assertThat(
@@ -668,10 +736,12 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             for (i in 0..text.length) {
                 assertThat(
@@ -690,10 +760,12 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             for (i in 0..ltrText.length) {
                 assertThat(
@@ -722,13 +794,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
 
@@ -749,13 +821,13 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(0), equalTo(0f))
 
@@ -778,14 +850,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..ltrText.length) {
                 assertThat(
@@ -816,14 +887,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
             // Notice that abc is
@@ -849,10 +919,12 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
         }
@@ -864,10 +936,12 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
         }
@@ -879,14 +953,13 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
         }
@@ -898,14 +971,13 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
         }
@@ -917,9 +989,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             for (i in 0..text.length) {
                 assertThat(
@@ -936,10 +1010,12 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             for (i in 0..text.length) {
                 assertThat(
@@ -958,12 +1034,14 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(
                     paragraph.getSecondaryHorizontal(i),
                     equalTo(fontSizeInPx * i)
@@ -985,13 +1063,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getSecondaryHorizontal(0), equalTo(0f))
 
@@ -1012,13 +1090,13 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getSecondaryHorizontal(0), equalTo(width))
 
@@ -1041,23 +1119,22 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(
                     paragraph.getSecondaryHorizontal(i),
                     equalTo(fontSizeInPx * i)
                 )
             }
 
-            for (i in 0 until rtlText.length) {
+            for (i in rtlText.indices) {
                 assertThat(
                     paragraph.getSecondaryHorizontal(i + ltrText.length),
                     equalTo(width - fontSizeInPx * i)
@@ -1079,14 +1156,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(
                 paragraph.getSecondaryHorizontal(0),
@@ -1114,10 +1190,12 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
         }
@@ -1129,10 +1207,12 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
         }
@@ -1144,14 +1224,13 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(width))
         }
@@ -1163,14 +1242,13 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
         }
@@ -1182,13 +1260,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
@@ -1202,14 +1279,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
@@ -1223,15 +1299,14 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until text.length) {
+            for (i in text.indices) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
             }
         }
@@ -1243,14 +1318,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
@@ -1266,13 +1340,12 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
@@ -1288,14 +1361,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
@@ -1311,14 +1383,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
@@ -1332,13 +1403,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
@@ -1352,14 +1422,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
@@ -1373,15 +1442,14 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until text.length) {
+            for (i in text.indices) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
             }
         }
@@ -1393,14 +1461,13 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0 until text.length - 1) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
@@ -1417,15 +1484,14 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
             }
 
@@ -1443,16 +1509,15 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
             }
 
@@ -1470,16 +1535,15 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
             }
 
@@ -1495,10 +1559,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             for (i in 0..text.lastIndex) {
                 assertThat(paragraph.getLineForOffset(i), equalTo(0))
@@ -1512,10 +1578,12 @@
             val text = "a\nb\nc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             for (i in 0..text.lastIndex) {
                 assertThat(paragraph.getLineForOffset(i), equalTo(i / 2))
@@ -1529,14 +1597,14 @@
             val text = "abcd"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                paragraphStyles = listOf(AnnotatedString.Item(ParagraphStyle(), 0, 2))
+                paragraphStyles = listOf(AnnotatedString.Item(ParagraphStyle(), 0, 2)),
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
             assertThat(paragraph.getLineForOffset(0), equalTo(0))
             assertThat(paragraph.getLineForOffset(1), equalTo(0))
             assertThat(paragraph.getLineForOffset(2), equalTo(1))
@@ -1550,14 +1618,14 @@
             val text = "abcd"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                paragraphStyles = listOf(AnnotatedString.Item(ParagraphStyle(), 2, 2))
+                paragraphStyles = listOf(AnnotatedString.Item(ParagraphStyle(), 2, 2)),
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
             assertThat(paragraph.getLineForOffset(0), equalTo(0))
             assertThat(paragraph.getLineForOffset(1), equalTo(0))
             // The empty paragraph takes one line
@@ -1572,10 +1640,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             paragraph.getLineForOffset(-1)
         }
@@ -1587,10 +1657,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleMultiParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             paragraph.getLineForOffset(text.length)
         }
@@ -1605,9 +1677,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -1638,9 +1710,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val firstLineLeft = paragraph.getLineLeft(0)
@@ -1685,9 +1757,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -1724,9 +1796,9 @@
         val paragraph = simpleMultiParagraph(
             text = text,
             fontFamily = fontFamilyMeasureFont,
-            fontSize = 20.sp
+            fontSize = 20.sp,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         val actualPath = paragraph.getPathForRange(1, 1)
 
@@ -1739,9 +1811,9 @@
         val paragraph = simpleMultiParagraph(
             text = text,
             fontFamily = fontFamilyMeasureFont,
-            fontSize = 20.sp
+            fontSize = 20.sp,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         val actualPath = paragraph.getPathForRange(0, 0)
 
@@ -1757,9 +1829,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineRight = paragraph.getLineRight(0)
@@ -1782,9 +1854,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineRight = paragraph.getLineRight(0)
@@ -1807,9 +1879,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineRight = paragraph.getLineRight(0)
@@ -1832,9 +1904,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -1865,9 +1937,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -1891,9 +1963,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -1914,9 +1986,9 @@
         val paragraph = simpleMultiParagraph(
             text = text,
             fontFamily = fontFamilyMeasureFont,
-            fontSize = 20.sp
+            fontSize = 20.sp,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         val result = paragraph.getWordBoundary(text.indexOf('a'))
 
@@ -1930,9 +2002,9 @@
         val paragraph = simpleMultiParagraph(
             text = text,
             fontFamily = fontFamilyMeasureFont,
-            fontSize = 20.sp
+            fontSize = 20.sp,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         val resultEnglish = paragraph.getWordBoundary(text.indexOf('a'))
         val resultHebrew = paragraph.getWordBoundary(text.indexOf('\u05d1'))
@@ -1943,75 +2015,15 @@
         assertThat(resultHebrew.end, equalTo(text.indexOf('\u05d2') + 1))
     }
 
-    @Test(expected = IllegalStateException::class)
-    fun width_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleMultiParagraph()
-
-        paragraph.width
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun height_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleMultiParagraph()
-
-        paragraph.height
-    }
-
-    @Test
-    fun minIntrinsicWidth_default_value() {
-        val paragraph = simpleMultiParagraph()
-
-        assertThat(paragraph.minIntrinsicWidth, equalTo(0.0f))
-    }
-
-    @Test
-    fun maxIntrinsicWidth_default_value() {
-        val paragraph = simpleMultiParagraph()
-
-        assertThat(paragraph.maxIntrinsicWidth, equalTo(0.0f))
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun firstBaseline_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleMultiParagraph()
-
-        paragraph.firstBaseline
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun lastBaseline_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleMultiParagraph()
-
-        paragraph.lastBaseline
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun didExceedMaxLines_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleMultiParagraph()
-
-        paragraph.didExceedMaxLines
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun paint_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleMultiParagraph()
-
-        paragraph.paint(mock())
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun getPositionForOffset_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleMultiParagraph()
-
-        paragraph.getOffsetForPosition(PxPosition.Origin)
-    }
-
     @Test(expected = AssertionError::class)
     fun getPathForRange_throws_exception_if_start_larger_than_end() {
         val text = "ab"
         val textStart = 0
         val textEnd = text.length
-        val paragraph = simpleMultiParagraph(text = text)
+        val paragraph = simpleMultiParagraph(
+            text = text,
+            constraints = ParagraphConstraints(Float.MAX_VALUE)
+        )
 
         paragraph.getPathForRange(textEnd, textStart)
     }
@@ -2021,7 +2033,10 @@
         val text = "ab"
         val textStart = 0
         val textEnd = text.length
-        val paragraph = simpleMultiParagraph(text = text)
+        val paragraph = simpleMultiParagraph(
+            text = text,
+            constraints = ParagraphConstraints(Float.MAX_VALUE)
+        )
 
         paragraph.getPathForRange(textStart - 2, textEnd - 1)
     }
@@ -2031,7 +2046,10 @@
         val text = "ab"
         val textStart = 0
         val textEnd = text.length
-        val paragraph = simpleMultiParagraph(text = text)
+        val paragraph = simpleMultiParagraph(
+            text = text,
+            constraints = ParagraphConstraints(Float.MAX_VALUE)
+        )
 
         paragraph.getPathForRange(textStart, textEnd + 1)
     }
@@ -2044,19 +2062,19 @@
             val fontSize = 20.sp
             val fontSizeInPx = fontSize.toPx().value
 
+            val layoutLTRWidth = (textLTR.length + 2) * fontSizeInPx
             val paragraphLTR = simpleMultiParagraph(
                 text = textLTR,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutLTRWidth)
             )
-            val layoutLTRWidth = (textLTR.length + 2) * fontSizeInPx
-            paragraphLTR.layout(ParagraphConstraints(width = layoutLTRWidth))
 
+            val layoutRTLWidth = (textRTL.length + 2) * fontSizeInPx
             val paragraphRTL = simpleMultiParagraph(
                 text = textRTL,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutRTLWidth)
             )
-            val layoutRTLWidth = (textRTL.length + 2) * fontSizeInPx
-            paragraphRTL.layout(ParagraphConstraints(width = layoutRTLWidth))
 
             // When textAlign is TextAlign.start, LTR aligns to left, RTL aligns to right.
             assertThat(paragraphLTR.getLineLeft(0), equalTo(0.0f))
@@ -2072,13 +2090,13 @@
             val fontSizeInPx = fontSize.toPx().value
 
             texts.map { text ->
+                val layoutWidth = (text.length + 2) * fontSizeInPx
                 val paragraph = simpleMultiParagraph(
                     text = text,
                     textAlign = TextAlign.Left,
-                    fontSize = fontSize
+                    fontSize = fontSize,
+                    constraints = ParagraphConstraints(width = layoutWidth)
                 )
-                val layoutWidth = (text.length + 2) * fontSizeInPx
-                paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
                 assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
             }
@@ -2093,13 +2111,13 @@
             val fontSizeInPx = fontSize.toPx().value
 
             texts.map { text ->
+                val layoutWidth = (text.length + 2) * fontSizeInPx
                 val paragraph = simpleMultiParagraph(
                     text = text,
                     textAlign = TextAlign.Right,
-                    fontSize = fontSize
+                    fontSize = fontSize,
+                    constraints = ParagraphConstraints(width = layoutWidth)
                 )
-                val layoutWidth = (text.length + 2) * fontSizeInPx
-                paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
                 assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
             }
@@ -2114,15 +2132,15 @@
             val fontSizeInPx = fontSize.toPx().value
 
             texts.map { text ->
+                val layoutWidth = (text.length + 2) * fontSizeInPx
                 val paragraph = simpleMultiParagraph(
                     text = text,
                     textAlign = TextAlign.Center,
-                    fontSize = fontSize
+                    fontSize = fontSize,
+                    constraints = ParagraphConstraints(width = layoutWidth)
                 )
-                val layoutWidth = (text.length + 2) * fontSizeInPx
-                paragraph.layout(ParagraphConstraints(width = layoutWidth))
-                val textWidth = text.length * fontSizeInPx
 
+                val textWidth = text.length * fontSizeInPx
                 assertThat(
                     paragraph.getLineLeft(0),
                     equalTo(layoutWidth / 2 - textWidth / 2)
@@ -2146,9 +2164,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 textAlign = TextAlign.Start,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
         }
@@ -2165,9 +2183,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 textAlign = TextAlign.End,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
         }
@@ -2184,9 +2202,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 textAlign = TextAlign.Start,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
         }
@@ -2203,9 +2221,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 textAlign = TextAlign.End,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
         }
@@ -2225,9 +2243,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 textAlign = TextAlign.Justify,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
             assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
@@ -2247,9 +2265,10 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
             // The position of the last character in display order.
             val position = PxPosition(("a.".length * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
             val charIndex = paragraph.getOffsetForPosition(position)
@@ -2268,9 +2287,10 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
             // The position of the first character in display order.
             val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
             val charIndex = paragraph.getOffsetForPosition(position)
@@ -2288,9 +2308,10 @@
 
             val paragraph = simpleMultiParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
             for (i in 0..text.length) {
                 // The position of the i-th character in display order.
                 val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
@@ -2310,10 +2331,11 @@
 
             val paragraph = simpleMultiParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
-            for (i in 0 until text.length) {
+
+            for (i in text.indices) {
                 // The position of the i-th character in display order.
                 val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
                 val charIndex = paragraph.getOffsetForPosition(position)
@@ -2332,9 +2354,10 @@
 
             val paragraph = simpleMultiParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
             // The first character in display order should be '.'
             val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
             val index = paragraph.getOffsetForPosition(position)
@@ -2355,9 +2378,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                lineHeight = lineHeight
+                lineHeight = lineHeight,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.lineCount, equalTo(4))
             // TODO(Migration/haoyuchang): Due to bug b/120530738, the height of the first line is
@@ -2387,9 +2410,9 @@
             val paragraph = simpleMultiParagraph(
                 text = text,
                 fontSize = fontSize,
-                lineHeight = lineHeight
+                lineHeight = lineHeight,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             val lastLine = paragraph.lineCount - 1
             // In the sample_font.ttf, the height of the line should be
@@ -2410,9 +2433,9 @@
                 text = text,
                 textIndent = TextIndent(firstLine = indent.px),
                 fontSize = fontSize,
-                fontFamily = fontFamilyMeasureFont
+                fontFamily = fontFamilyMeasureFont,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             // This position should point to the first character 'a' if indent is applied.
             // Otherwise this position will point to the second character 'b'.
@@ -2435,9 +2458,9 @@
                 text = text,
                 textIndent = TextIndent(firstLine = indent.px),
                 fontSize = fontSize,
-                fontFamily = fontFamilyMeasureFont
+                fontFamily = fontFamilyMeasureFont,
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             assertThat(paragraph.lineCount, equalTo(2))
             // This position should point to the first character of the first line if indent is
@@ -2464,9 +2487,9 @@
                     restLine = indent.px
                 ),
                 fontSize = fontSize,
-                fontFamily = fontFamilyMeasureFont
+                fontFamily = fontFamilyMeasureFont,
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             // This position should point to the first character of the second line if indent is
             // applied. Otherwise this position will point to the second character of the second line.
@@ -2491,6 +2514,7 @@
         fontFamily: FontFamily = fontFamilyMeasureFont,
         localeList: LocaleList? = null,
         textStyle: TextStyle? = null,
+        constraints: ParagraphConstraints,
         density: Density? = null,
         layoutDirection: LayoutDirection = LayoutDirection.Ltr,
         textDirectionAlgorithm: TextDirectionAlgorithm? = null
@@ -2513,6 +2537,7 @@
                 lineHeight = lineHeight
             ),
             maxLines = maxLines,
+            constraints = constraints,
             density = density ?: defaultDensity,
             layoutDirection = layoutDirection,
             resourceLoader = TestFontResourceLoader(context)
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
index d9e5d89..77760aa 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
@@ -28,8 +28,8 @@
 import androidx.ui.core.withDensity
 import androidx.ui.engine.geometry.Offset
 import androidx.ui.engine.geometry.Rect
-import androidx.ui.graphics.Color
 import androidx.ui.graphics.Canvas
+import androidx.ui.graphics.Color
 import androidx.ui.graphics.Image
 import androidx.ui.graphics.ImageConfig
 import androidx.ui.graphics.Path
@@ -53,7 +53,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
 import kotlin.math.roundToInt
 
 @RunWith(JUnit4::class)
@@ -77,9 +76,11 @@
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
             val text = ""
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = 100.0f))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = 100.0f)
+            )
 
             assertThat(paragraph.width, equalTo(100.0f))
 
@@ -89,8 +90,6 @@
             assertThat(paragraph.lastBaseline, equalTo(fontSizeInPx * 0.8f))
             assertThat(paragraph.maxIntrinsicWidth, equalTo(0.0f))
             assertThat(paragraph.minIntrinsicWidth, equalTo(0.0f))
-            // TODO(Migration/siyamed): no baseline query per line?
-            // TODO(Migration/siyamed): no line count?
         }
     }
 
@@ -101,10 +100,12 @@
             val fontSizeInPx = fontSize.toPx().value
 
             for (text in arrayOf("xyz", "\u05D0\u05D1\u05D2")) {
-                val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-                // width greater than text width - 150
-                paragraph.layout(ParagraphConstraints(width = 200.0f))
+                val paragraph = simpleParagraph(
+                    text = text,
+                    fontSize = fontSize,
+                    // width greater than text width - 150
+                    constraints = ParagraphConstraints(width = 200.0f)
+                )
 
                 assertThat(text, paragraph.width, equalTo(200.0f))
                 assertThat(text, paragraph.height, equalTo(fontSizeInPx))
@@ -128,10 +129,12 @@
             val fontSizeInPx = fontSize.toPx().value
 
             for (text in arrayOf("abcdef", "\u05D0\u05D1\u05D2\u05D3\u05D4\u05D5")) {
-                val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-                // 3 chars width
-                paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+                val paragraph = simpleParagraph(
+                    text = text,
+                    fontSize = fontSize,
+                    // 3 chars width
+                    constraints = ParagraphConstraints(width = 3 * fontSizeInPx)
+                )
 
                 // 3 chars
                 assertThat(text, paragraph.width, equalTo(3 * fontSizeInPx))
@@ -165,13 +168,14 @@
             val fontSizeInPx = fontSize.toPx().value
 
             for (text in arrayOf("abc\ndef", "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5")) {
-                val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-                // 3 chars width
-                paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+                val paragraph = simpleParagraph(
+                    text = text,
+                    fontSize = fontSize,
+                    // 3 chars width
+                    constraints = ParagraphConstraints(width = 3 * fontSizeInPx)
+                )
 
                 // 3 chars
-
                 assertThat(text, paragraph.width, equalTo(3 * fontSizeInPx))
                 // 2 lines, 1 line gap
                 assertThat(
@@ -205,11 +209,12 @@
             val fontSizeInPx = fontSize.toPx().value
 
             for (text in arrayOf("abc\ndef", "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5")) {
-                val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-                // 2 chars width
-
-                paragraph.layout(ParagraphConstraints(width = 2 * fontSizeInPx))
+                val paragraph = simpleParagraph(
+                    text = text,
+                    fontSize = fontSize,
+                    // 2 chars width
+                    constraints = ParagraphConstraints(width = 2 * fontSizeInPx)
+                )
 
                 // 2 chars
                 assertThat(text, paragraph.width, equalTo(2 * fontSizeInPx))
@@ -244,9 +249,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
             // test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars 0, 1, 2 ...
             for (i in 0..text.length) {
                 val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
@@ -266,9 +274,11 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             // test positions that are 1, fontSize+1, 2fontSize+1 which maps to chars .., 2, 1, 0
             for (i in 0..text.length) {
@@ -291,9 +301,11 @@
             val text = firstLine + secondLine
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = firstLine.length * fontSizeInPx)
+            )
 
             // test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
             // which maps to chars 3, 4, 5
@@ -317,9 +329,11 @@
             val text = firstLine + secondLine
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = firstLine.length * fontSizeInPx)
+            )
 
             // test positions are 1, fontSize+1, 2fontSize+1 and always on the second line
             // which maps to chars 5, 4, 3
@@ -341,9 +355,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             // greater than width
             var position = PxPosition((fontSizeInPx * text.length * 2).px, (fontSizeInPx / 2).px)
@@ -363,9 +379,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             // greater than height
             var position = PxPosition((fontSizeInPx / 2).px, (fontSizeInPx * text.length * 2).px)
@@ -385,9 +403,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
             // test positions that are 0, 1, 2 ... which maps to chars 0, 1, 2 ...
             for (i in 0..text.length - 1) {
                 val box = paragraph.getBoundingBox(i)
@@ -407,13 +428,15 @@
             val text = firstLine + secondLine
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = firstLine.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = firstLine.length * fontSizeInPx)
+            )
 
             // test positions are 3, 4, 5 and always on the second line
             // which maps to chars 3, 4, 5
-            for (i in 0..secondLine.length - 1) {
+            for (i in secondLine.indices) {
                 val textPosition = i + firstLine.length
                 val box = paragraph.getBoundingBox(textPosition)
                 assertThat(box.left, equalTo(i * fontSizeInPx))
@@ -430,9 +453,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             val textPosition = -1
             val box = paragraph.getBoundingBox(textPosition)
@@ -451,9 +476,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             val textPosition = text.length + 1
             paragraph.getBoundingBox(textPosition)
@@ -466,9 +493,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             paragraph.getCursorRect(text.length + 1)
         }
@@ -480,9 +509,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             paragraph.getCursorRect(-1)
         }
@@ -494,11 +525,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
-
-            for (i in 0 until text.length) {
+            for (i in text.indices) {
                 val cursorRect = paragraph.getCursorRect(i)
                 val cursorXOffset = i * fontSizeInPx
                 assertThat(
@@ -520,10 +553,12 @@
             val text = "abcdef"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val charsPerLine = 3
-
-            paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = charsPerLine * fontSizeInPx)
+            )
 
             for (i in 0 until charsPerLine) {
                 val cursorXOffset = i * fontSizeInPx
@@ -559,9 +594,11 @@
             val text = "abc\ndef"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+            )
 
             // Cursor before '\n'
             assertThat(
@@ -593,9 +630,11 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+            )
 
             // Cursor before '\n'
             assertThat(
@@ -627,11 +666,13 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
-
-            for (i in 0 until text.length) {
+            for (i in text.indices) {
                 val cursorXOffset = (text.length - i) * fontSizeInPx
                 assertThat(
                     paragraph.getCursorRect(i),
@@ -652,10 +693,12 @@
             val text = "\u05D0\u05D1\u05D2\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val charsPerLine = 3
-
-            paragraph.layout(ParagraphConstraints(width = charsPerLine * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = charsPerLine * fontSizeInPx)
+            )
 
             for (i in 0 until charsPerLine) {
                 val cursorXOffset = (charsPerLine - i) * fontSizeInPx
@@ -691,9 +734,11 @@
             val text = "\u05D0\u05D1\u05D2\n\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = 3 * fontSizeInPx)
+            )
 
             // Cursor before '\n'
             assertThat(
@@ -725,9 +770,11 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = 3 * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = 3 * fontSizeInPx)
+            )
 
             // Cursor before '\n'
             assertThat(
@@ -759,9 +806,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             for (i in 0..text.length) {
                 assertThat(
@@ -778,10 +827,12 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             for (i in 0..text.length) {
                 assertThat(
@@ -800,10 +851,12 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             for (i in 0..ltrText.length) {
                 assertThat(
@@ -832,13 +885,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
 
@@ -859,13 +912,13 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(0), equalTo(0f))
 
@@ -888,14 +941,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..ltrText.length) {
                 assertThat(
@@ -926,14 +978,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(0), equalTo(width))
             for (i in 1 until ltrText.length) {
@@ -958,10 +1009,12 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
         }
@@ -973,10 +1026,12 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
         }
@@ -988,14 +1043,13 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(width))
         }
@@ -1007,14 +1061,13 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getPrimaryHorizontal(text.length), equalTo(0f))
         }
@@ -1026,9 +1079,11 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
-
-            paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = text.length * fontSizeInPx)
+            )
 
             for (i in 0..text.length) {
                 assertThat(
@@ -1045,10 +1100,12 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             for (i in 0..text.length) {
                 assertThat(
@@ -1067,12 +1124,14 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(
                     paragraph.getSecondaryHorizontal(i),
                     equalTo(fontSizeInPx * i)
@@ -1094,13 +1153,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getSecondaryHorizontal(0), equalTo(0f))
 
@@ -1121,13 +1180,13 @@
             val text = "\u05D0\u05D1\u05D2"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getSecondaryHorizontal(0), equalTo(width))
 
@@ -1150,23 +1209,22 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(
                     paragraph.getSecondaryHorizontal(i),
                     equalTo(fontSizeInPx * i)
                 )
             }
 
-            for (i in 0 until rtlText.length) {
+            for (i in rtlText.indices) {
                 assertThat(
                     paragraph.getSecondaryHorizontal(i + ltrText.length),
                     equalTo(width - fontSizeInPx * i)
@@ -1188,14 +1246,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(
                 paragraph.getSecondaryHorizontal(0),
@@ -1223,10 +1280,12 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
         }
@@ -1238,10 +1297,12 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize)
             val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
+            )
 
             assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
         }
@@ -1253,14 +1314,13 @@
             val text = "abc\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(width))
         }
@@ -1272,14 +1332,13 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
         }
@@ -1291,13 +1350,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
@@ -1311,14 +1369,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
@@ -1332,15 +1389,14 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until text.length) {
+            for (i in text.indices) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
             }
         }
@@ -1352,14 +1408,13 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
@@ -1375,13 +1430,12 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
@@ -1397,14 +1451,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
@@ -1420,14 +1473,13 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
@@ -1441,13 +1493,12 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
@@ -1461,14 +1512,13 @@
             val text = "abc"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0..text.length) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
@@ -1482,15 +1532,14 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until text.length) {
+            for (i in text.indices) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
             }
         }
@@ -1502,14 +1551,13 @@
             val text = "\u05D0\u05D1\u05D2\n"
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
-
-            paragraph.layout(ParagraphConstraints(width))
 
             for (i in 0 until text.length - 1) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
@@ -1526,15 +1574,14 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
             }
 
@@ -1552,16 +1599,15 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
             }
 
@@ -1579,16 +1625,15 @@
             val text = ltrText + rtlText
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
+            val width = text.length * fontSizeInPx
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl
+                textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
+                constraints = ParagraphConstraints(width)
             )
-            val width = text.length * fontSizeInPx
 
-            paragraph.layout(ParagraphConstraints(width))
-
-            for (i in 0 until ltrText.length) {
+            for (i in ltrText.indices) {
                 assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
             }
 
@@ -1623,12 +1668,11 @@
                     paragraphStyle = ParagraphStyle(),
                     density = defaultDensity,
                     resourceLoader = resourceLoader,
-                    layoutDirection = LayoutDirection.Ltr
+                    layoutDirection = LayoutDirection.Ltr,
+                    // just have 10x font size to have a bitmap
+                    constraints = ParagraphConstraints(width = fontSizeInPx * 10)
                 )
 
-                // just have 10x font size to have a bitmap
-                paragraph.layout(ParagraphConstraints(width = fontSizeInPx * 10))
-
                 paragraph.bitmap()
             }
 
@@ -1647,9 +1691,10 @@
         val paragraph = simpleParagraph(
             text = text,
             fontSize = 100.sp,
-            maxLines = maxLines
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
         assertThat(paragraph.height, equalTo(0f))
     }
 
@@ -1657,12 +1702,12 @@
     fun maxLines_withMaxLineNegative_throwsException() {
         val text = "a\na\na"
         val maxLines = -1
-        val paragraph = simpleParagraph(
+        simpleParagraph(
             text = text,
             fontSize = 100.sp,
-            maxLines = maxLines
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
     }
 
     @Test
@@ -1676,9 +1721,10 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                maxLines = maxLines
+                maxLines = maxLines,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
             val expectHeight = (maxLines + (maxLines - 1) * 0.2f) * fontSizeInPx
             assertThat(paragraph.height, equalTo(expectHeight))
         }
@@ -1695,9 +1741,10 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                maxLines = maxLines
+                maxLines = maxLines,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
             val expectFirstBaseline = 0.8f * fontSizeInPx
             assertThat(paragraph.firstBaseline, equalTo(expectFirstBaseline))
             val expectLastBaseline =
@@ -1716,9 +1763,10 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                maxLines = maxLines
+                maxLines = maxLines,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+
             val expectHeight = (maxLines + (maxLines - 1) * 0.2f) * fontSizeInPx
             assertThat(paragraph.height, equalTo(expectHeight))
         }
@@ -1735,9 +1783,10 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                maxLines = maxLines
+                maxLines = maxLines,
+                constraints = ParagraphConstraints(width = 200f)
             )
-            paragraph.layout(ParagraphConstraints(width = 200f))
+
             val expectHeight = (lineCount + (lineCount - 1) * 0.2f) * fontSizeInPx
             assertThat(paragraph.height, equalTo(expectHeight))
         }
@@ -1754,15 +1803,15 @@
             val paragraphWithMaxLine = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                maxLines = maxLines
+                maxLines = maxLines,
+                constraints = ParagraphConstraints(width = fontSizeInPx)
             )
-            paragraphWithMaxLine.layout(ParagraphConstraints(width = fontSizeInPx))
 
             val paragraphNoMaxLine = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = fontSizeInPx)
             )
-            paragraphNoMaxLine.layout(ParagraphConstraints(width = fontSizeInPx))
 
             // Make sure the maxLine is applied correctly
             assertThat(paragraphNoMaxLine.height, greaterThan(paragraphWithMaxLine.height))
@@ -1792,9 +1841,12 @@
     fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_returnsTrue() {
         val text = "aaa\naa"
         val maxLines = text.lines().size - 1
-        val paragraph = simpleParagraph(text = text, maxLines = maxLines)
+        val paragraph = simpleParagraph(
+            text = text,
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+        )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
         assertThat(paragraph.didExceedMaxLines, equalTo(true))
     }
 
@@ -1802,9 +1854,12 @@
     fun didExceedMaxLines_withMaxLinesEqualToTextLines_returnsFalse() {
         val text = "aaa\naa"
         val maxLines = text.lines().size
-        val paragraph = simpleParagraph(text = text, maxLines = maxLines)
+        val paragraph = simpleParagraph(
+            text = text,
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+        )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
         assertThat(paragraph.didExceedMaxLines, equalTo(false))
     }
 
@@ -1812,9 +1867,12 @@
     fun didExceedMaxLines_withMaxLinesGreaterThanTextLines_returnsFalse() {
         val text = "aaa\naa"
         val maxLines = text.lines().size + 1
-        val paragraph = simpleParagraph(text = text, maxLines = maxLines)
+        val paragraph = simpleParagraph(
+            text = text,
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+        )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
         assertThat(paragraph.didExceedMaxLines, equalTo(false))
     }
 
@@ -1825,10 +1883,14 @@
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
             val maxLines = 1
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize, maxLines = maxLines)
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                maxLines = maxLines,
+                // One line can only contain 1 character
+                constraints = ParagraphConstraints(width = fontSizeInPx)
+            )
 
-            // One line can only contain 1 character
-            paragraph.layout(ParagraphConstraints(width = fontSizeInPx))
             assertThat(paragraph.didExceedMaxLines, equalTo(true))
         }
     }
@@ -1837,9 +1899,13 @@
     fun didExceedMaxLines_withMaxLinesEqualToTextLines_withLineWrap_returnsFalse() {
         val text = "a"
         val maxLines = text.lines().size
-        val paragraph = simpleParagraph(text = text, fontSize = 50.sp, maxLines = maxLines)
+        val paragraph = simpleParagraph(
+            text = text,
+            fontSize = 50.sp,
+            maxLines = maxLines,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+        )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
         assertThat(paragraph.didExceedMaxLines, equalTo(false))
     }
 
@@ -1850,10 +1916,14 @@
             val maxLines = 3
             val fontSize = 50.sp
             val fontSizeInPx = fontSize.toPx().value
-            val paragraph = simpleParagraph(text = text, fontSize = fontSize, maxLines = maxLines)
+            val paragraph = simpleParagraph(
+                text = text,
+                fontSize = fontSize,
+                maxLines = maxLines,
+                // One line can only contain 1 character
+                constraints = ParagraphConstraints(width = fontSizeInPx)
+            )
 
-            // One line can only contain 1 character
-            paragraph.layout(ParagraphConstraints(width = fontSizeInPx))
             assertThat(paragraph.didExceedMaxLines, equalTo(false))
         }
     }
@@ -1866,19 +1936,19 @@
             val fontSize = 20.sp
             val fontSizeInPx = fontSize.toPx().value
 
+            val layoutLTRWidth = (textLTR.length + 2) * fontSizeInPx
             val paragraphLTR = simpleParagraph(
                 text = textLTR,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutLTRWidth)
             )
-            val layoutLTRWidth = (textLTR.length + 2) * fontSizeInPx
-            paragraphLTR.layout(ParagraphConstraints(width = layoutLTRWidth))
 
+            val layoutRTLWidth = (textRTL.length + 2) * fontSizeInPx
             val paragraphRTL = simpleParagraph(
                 text = textRTL,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutRTLWidth)
             )
-            val layoutRTLWidth = (textRTL.length + 2) * fontSizeInPx
-            paragraphRTL.layout(ParagraphConstraints(width = layoutRTLWidth))
 
             // When textAlign is TextAlign.start, LTR aligns to left, RTL aligns to right.
             assertThat(paragraphLTR.getLineLeft(0), equalTo(0.0f))
@@ -1894,13 +1964,13 @@
             val fontSizeInPx = fontSize.toPx().value
 
             texts.map { text ->
+                val layoutWidth = (text.length + 2) * fontSizeInPx
                 val paragraph = simpleParagraph(
                     text = text,
                     textAlign = TextAlign.Left,
-                    fontSize = fontSize
+                    fontSize = fontSize,
+                    constraints = ParagraphConstraints(width = layoutWidth)
                 )
-                val layoutWidth = (text.length + 2) * fontSizeInPx
-                paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
                 assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
             }
@@ -1915,13 +1985,13 @@
             val fontSizeInPx = fontSize.toPx().value
 
             texts.map { text ->
+                val layoutWidth = (text.length + 2) * fontSizeInPx
                 val paragraph = simpleParagraph(
                     text = text,
                     textAlign = TextAlign.Right,
-                    fontSize = fontSize
+                    fontSize = fontSize,
+                    constraints = ParagraphConstraints(width = layoutWidth)
                 )
-                val layoutWidth = (text.length + 2) * fontSizeInPx
-                paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
                 assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
             }
@@ -1936,15 +2006,15 @@
             val fontSizeInPx = fontSize.toPx().value
 
             texts.map { text ->
+                val layoutWidth = (text.length + 2) * fontSizeInPx
                 val paragraph = simpleParagraph(
                     text = text,
                     textAlign = TextAlign.Center,
-                    fontSize = fontSize
+                    fontSize = fontSize,
+                    constraints = ParagraphConstraints(width = layoutWidth)
                 )
-                val layoutWidth = (text.length + 2) * fontSizeInPx
-                paragraph.layout(ParagraphConstraints(width = layoutWidth))
-                val textWidth = text.length * fontSizeInPx
 
+                val textWidth = text.length * fontSizeInPx
                 assertThat(
                     paragraph.getLineLeft(0),
                     equalTo(layoutWidth / 2 - textWidth / 2)
@@ -1968,9 +2038,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textAlign = TextAlign.Start,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
         }
@@ -1987,9 +2057,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textAlign = TextAlign.End,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
         }
@@ -2006,9 +2076,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textAlign = TextAlign.Start,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
         }
@@ -2025,9 +2095,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textAlign = TextAlign.End,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
         }
@@ -2047,9 +2117,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textAlign = TextAlign.Justify,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.getLineLeft(0), equalTo(0.0f))
             assertThat(paragraph.getLineRight(0), equalTo(layoutWidth))
@@ -2069,9 +2139,10 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textDirectionAlgorithm = TextDirectionAlgorithm.ForceLtr,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
             // The position of the last character in display order.
             val position = PxPosition(("a.".length * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
             val charIndex = paragraph.getOffsetForPosition(position)
@@ -2090,9 +2161,10 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textDirectionAlgorithm = TextDirectionAlgorithm.ForceRtl,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
             // The position of the first character in display order.
             val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
             val charIndex = paragraph.getOffsetForPosition(position)
@@ -2110,9 +2182,10 @@
 
             val paragraph = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
             for (i in 0..text.length) {
                 // The position of the i-th character in display order.
                 val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
@@ -2132,10 +2205,11 @@
 
             val paragraph = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
-            for (i in 0 until text.length) {
+
+            for (i in text.indices) {
                 // The position of the i-th character in display order.
                 val position = PxPosition((i * fontSizeInPx + 1).px, (fontSizeInPx / 2).px)
                 val charIndex = paragraph.getOffsetForPosition(position)
@@ -2154,9 +2228,10 @@
 
             val paragraph = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
+
             // The first character in display order should be '.'
             val position = PxPosition((fontSizeInPx / 2 + 1).px, (fontSizeInPx / 2).px)
             val index = paragraph.getOffsetForPosition(position)
@@ -2177,9 +2252,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                lineHeight = lineHeight
+                lineHeight = lineHeight,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             assertThat(paragraph.lineCount, equalTo(4))
             // TODO(Migration/haoyuchang): Due to bug b/120530738, the height of the first line is
@@ -2209,9 +2284,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontSize = fontSize,
-                lineHeight = lineHeight
+                lineHeight = lineHeight,
+                constraints = ParagraphConstraints(width = layoutWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = layoutWidth))
 
             val lastLine = paragraph.lineCount - 1
             // In the sample_font.ttf, the height of the line should be
@@ -2231,9 +2306,9 @@
 
             val paragraph = simpleParagraph(
                 text = text,
-                textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+                textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             // Make sure there is only one line, so that we can use getLineRight to test fontSize.
             assertThat(paragraph.lineCount, equalTo(1))
@@ -2256,9 +2331,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             // Make sure there is only one line, so that we can use getLineRight to test fontSize.
             assertThat(paragraph.lineCount, equalTo(1))
@@ -2287,9 +2362,9 @@
                 textStyles = listOf(
                     AnnotatedString.Item(textStyle, 0, text.length),
                     AnnotatedString.Item(textStyleOverwrite, 0, "abc".length)
-                )
+                ),
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             // Make sure there is only one line, so that we can use getLineRight to test fontSize.
             assertThat(paragraph.lineCount, equalTo(1))
@@ -2311,9 +2386,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             assertThat(
                 paragraph.getLineRight(0),
@@ -2340,9 +2415,9 @@
                     AnnotatedString.Item(textStyle, 0, text.length),
                     AnnotatedString.Item(textStyleNested, 0, text.length)
                 ),
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             assertThat(
                 paragraph.getLineRight(0),
@@ -2370,9 +2445,9 @@
                     AnnotatedString.Item(fontSizeStyle, 0, text.length),
                     AnnotatedString.Item(fontSizeScaleStyle, 0, text.length)
                 ),
-                fontSize = paragraphFontSize
+                fontSize = paragraphFontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             assertThat(
                 paragraph.getLineRight(0),
@@ -2400,9 +2475,9 @@
                     AnnotatedString.Item(fontSizeScaleStyle, 0, text.length),
                     AnnotatedString.Item(fontSizeStyle, 0, text.length)
                 ),
-                fontSize = paragraphFontSize
+                fontSize = paragraphFontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             assertThat(
                 paragraph.getLineRight(0),
@@ -2434,9 +2509,9 @@
                     AnnotatedString.Item(fontSizeStyle, 0, text.length),
                     AnnotatedString.Item(fontSizeScaleStyle2, 0, text.length)
                 ),
-                fontSize = paragraphFontSize
+                fontSize = paragraphFontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             assertThat(
                 paragraph.getLineRight(0),
@@ -2458,9 +2533,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             // Make sure there is only one line, so that we can use getLineRight to test fontSize.
             assertThat(paragraph.lineCount, equalTo(1))
@@ -2485,9 +2560,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             // Make sure there is only one line, so that we can use getLineRight to test fontSize.
             assertThat(paragraph.lineCount, equalTo(1))
@@ -2517,9 +2592,9 @@
                     AnnotatedString.Item(textStyle, 0, text.length),
                     AnnotatedString.Item(textStyleOverwrite, 0, "abc".length)
                 ),
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             // Make sure there is only one line, so that we can use getLineRight to test fontSize.
             assertThat(paragraph.lineCount, equalTo(1))
@@ -2542,9 +2617,9 @@
                 text = text,
                 textIndent = TextIndent(firstLine = indent.px),
                 fontSize = fontSize,
-                fontFamily = fontFamilyMeasureFont
+                fontFamily = fontFamilyMeasureFont,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             // This position should point to the first character 'a' if indent is applied.
             // Otherwise this position will point to the second character 'b'.
@@ -2567,9 +2642,9 @@
                 text = text,
                 textIndent = TextIndent(firstLine = indent.px),
                 fontSize = fontSize,
-                fontFamily = fontFamilyMeasureFont
+                fontFamily = fontFamilyMeasureFont,
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             assertThat(paragraph.lineCount, equalTo(2))
             // This position should point to the first character of the first line if indent is
@@ -2596,9 +2671,9 @@
                     restLine = indent.px
                 ),
                 fontSize = fontSize,
-                fontFamily = fontFamilyMeasureFont
+                fontFamily = fontFamilyMeasureFont,
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             // This position should point to the first character of the second line if indent is
             // applied. Otherwise this position will point to the second character of the second line.
@@ -2630,9 +2705,9 @@
                     AnnotatedString.Item(textStyle, "a".length, text.length)
                 ),
                 fontSize = fontSize,
-                fontFamily = fontFamilyCustom100
+                fontFamily = fontFamilyCustom100,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             assertThat(paragraph.lineCount, equalTo(1))
             assertThat(paragraph.getLineWidth(0), equalTo(expectedWidth))
@@ -2654,9 +2729,9 @@
                     AnnotatedString.Item(textStyle, 0, "aA".length)
                 ),
                 fontSize = fontSize,
-                fontFamily = fontFamilyKernFont
+                fontFamily = fontFamilyKernFont,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             // Two characters are kerning, so minus 0.4 * fontSize
             val expectedWidth = text.length * fontSizeInPx - 0.4f * fontSizeInPx
@@ -2685,12 +2760,14 @@
                 text = text,
                 textStyles = listOf(
                     AnnotatedString.Item(textStyle, 0, text.length)
-                )
+                ),
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraphShadow.layout(ParagraphConstraints(width = paragraphWidth))
 
-            val paragraph = simpleParagraph(text = text)
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
+            val paragraph = simpleParagraph(
+                text = text,
+                constraints = ParagraphConstraints(width = paragraphWidth)
+            )
 
             assertThat(paragraphShadow.bitmap(), not(equalToBitmap(paragraph.bitmap())))
         }
@@ -2708,16 +2785,16 @@
 
             val paragraphWithoutColor = simpleParagraph(
                 text = text,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(paragraphWidth)
             )
-            paragraphWithoutColor.layout(ParagraphConstraints(paragraphWidth))
 
             val paragraphWithColor = simpleParagraph(
                 text = text,
                 textStyle = textStyle,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(paragraphWidth)
             )
-            paragraphWithColor.layout(ParagraphConstraints(paragraphWidth))
 
             assertThat(
                 paragraphWithColor.bitmap(),
@@ -2739,9 +2816,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 textStyle = textStyle,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(Float.MAX_VALUE))
 
             assertThat(
                 paragraph.getLineRight(0),
@@ -2759,9 +2836,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -2792,9 +2869,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val firstLineLeft = paragraph.getLineLeft(0)
@@ -2839,9 +2916,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -2878,9 +2955,9 @@
         val paragraph = simpleParagraph(
             text = text,
             fontFamily = fontFamilyMeasureFont,
-            fontSize = 20.sp
+            fontSize = 20.sp,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         val actualPath = paragraph.getPathForRange(1, 1)
 
@@ -2893,9 +2970,9 @@
         val paragraph = simpleParagraph(
             text = text,
             fontFamily = fontFamilyMeasureFont,
-            fontSize = 20.sp
+            fontSize = 20.sp,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         val actualPath = paragraph.getPathForRange(0, 0)
 
@@ -2911,9 +2988,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineRight = paragraph.getLineRight(0)
@@ -2936,9 +3013,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineRight = paragraph.getLineRight(0)
@@ -2961,9 +3038,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineRight = paragraph.getLineRight(0)
@@ -2986,9 +3063,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -3019,9 +3096,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -3045,9 +3122,9 @@
             val paragraph = simpleParagraph(
                 text = text,
                 fontFamily = fontFamilyMeasureFont,
-                fontSize = fontSize
+                fontSize = fontSize,
+                constraints = ParagraphConstraints(width = Float.MAX_VALUE)
             )
-            paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
             val expectedPath = Path()
             val lineLeft = paragraph.getLineLeft(0)
@@ -3068,9 +3145,9 @@
         val paragraph = simpleParagraph(
             text = text,
             fontFamily = fontFamilyMeasureFont,
-            fontSize = 20.sp
+            fontSize = 20.sp,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         val result = paragraph.getWordBoundary(text.indexOf('a'))
 
@@ -3084,9 +3161,9 @@
         val paragraph = simpleParagraph(
             text = text,
             fontFamily = fontFamilyMeasureFont,
-            fontSize = 20.sp
+            fontSize = 20.sp,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         val resultEnglish = paragraph.getWordBoundary(text.indexOf('a'))
         val resultHebrew = paragraph.getWordBoundary(text.indexOf('\u05d1'))
@@ -3106,16 +3183,16 @@
         val paragraph = simpleParagraph(
             text = text,
             textStyle = TextStyle(fontSize = fontSize),
-            density = Density(density = 1f, fontScale = 1f)
+            density = Density(density = 1f, fontScale = 1f),
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         val doubleFontSizeParagraph = simpleParagraph(
             text = text,
             textStyle = TextStyle(fontSize = fontSize),
-            density = Density(density = 1f, fontScale = densityMultiplier)
+            density = Density(density = 1f, fontScale = densityMultiplier),
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        doubleFontSizeParagraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         assertThat(
             doubleFontSizeParagraph.maxIntrinsicWidth,
@@ -3124,33 +3201,16 @@
         assertThat(doubleFontSizeParagraph.height, equalTo(paragraph.height * densityMultiplier))
     }
 
-    @Test(expected = IllegalStateException::class)
-    fun width_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleParagraph()
-
-        paragraph.width
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun height_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleParagraph()
-
-        paragraph.height
-    }
-
-    @Test
-    fun minIntrinsicWidth_default_value() {
-        val paragraph = simpleParagraph()
-
-        assertThat(paragraph.minIntrinsicWidth, equalTo(0.0f))
-    }
-
     @Test
     fun minInstrinsicWidth_includes_white_space() {
         withDensity(defaultDensity) {
             val fontSize = 12.sp
             val text = "b "
-            val paragraph = simpleParagraph(text = text, textStyle = TextStyle(fontSize = fontSize))
+            val paragraph = simpleParagraph(
+                text = text,
+                textStyle = TextStyle(fontSize = fontSize),
+                constraints = ParagraphConstraints(Float.MAX_VALUE)
+            )
 
             val expectedWidth = text.length * fontSize.toPx().value
             assertThat(paragraph.minIntrinsicWidth, equalTo(expectedWidth))
@@ -3166,7 +3226,11 @@
                 string + "a".repeat(next) + " "
             }
             val fontSize = 12.sp
-            val paragraph = simpleParagraph(text = text, textStyle = TextStyle(fontSize = fontSize))
+            val paragraph = simpleParagraph(
+                text = text,
+                textStyle = TextStyle(fontSize = fontSize),
+                constraints = ParagraphConstraints(Float.MAX_VALUE)
+            )
 
             // +1 is for the white space
             val expectedWidth = (maxWordLength + 1) * fontSize.toPx().value
@@ -3187,7 +3251,8 @@
                     AnnotatedString.Item(
                         TextStyle(fontSize = styledFontSize), "a".length, "a bb ".length
                     )
-                )
+                ),
+                constraints = ParagraphConstraints(Float.MAX_VALUE)
             )
 
             val expectedWidth = "bb ".length * styledFontSize.toPx().value
@@ -3195,54 +3260,15 @@
         }
     }
 
-    @Test
-    fun maxIntrinsicWidth_default_value() {
-        val paragraph = simpleParagraph()
-
-        assertThat(paragraph.maxIntrinsicWidth, equalTo(0.0f))
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun firstBaseline_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleParagraph()
-
-        paragraph.firstBaseline
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun lastBaseline_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleParagraph()
-
-        paragraph.lastBaseline
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun didExceedMaxLines_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleParagraph()
-
-        paragraph.didExceedMaxLines
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun paint_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleParagraph()
-
-        paragraph.paint(mock(Canvas::class.java))
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun getOffsetForPosition_throws_exception_if_layout_is_not_called() {
-        val paragraph = simpleParagraph()
-
-        paragraph.getOffsetForPosition(PxPosition.Origin)
-    }
-
     @Test(expected = AssertionError::class)
     fun getPathForRange_throws_exception_if_start_larger_than_end() {
         val text = "ab"
         val textStart = 0
         val textEnd = text.length
-        val paragraph = simpleParagraph(text = text)
+        val paragraph = simpleParagraph(
+            text = text,
+            constraints = ParagraphConstraints(Float.MAX_VALUE)
+        )
 
         paragraph.getPathForRange(textEnd, textStart)
     }
@@ -3252,7 +3278,10 @@
         val text = "ab"
         val textStart = 0
         val textEnd = text.length
-        val paragraph = simpleParagraph(text = text)
+        val paragraph = simpleParagraph(
+            text = text,
+            constraints = ParagraphConstraints(Float.MAX_VALUE)
+        )
 
         paragraph.getPathForRange(textStart - 2, textEnd - 1)
     }
@@ -3262,7 +3291,10 @@
         val text = "ab"
         val textStart = 0
         val textEnd = text.length
-        val paragraph = simpleParagraph(text = text)
+        val paragraph = simpleParagraph(
+            text = text,
+            constraints = ParagraphConstraints(Float.MAX_VALUE)
+        )
 
         paragraph.getPathForRange(textStart, textEnd + 1)
     }
@@ -3287,9 +3319,10 @@
                 resourceLoader = TestFontResourceLoader(context)
             )
 
-            val paragraph = Paragraph(paragraphIntrinsics = paragraphIntrinsics)
-
-            paragraph.layout(ParagraphConstraints(fontSizeInPx * text.length))
+            val paragraph = Paragraph(
+                paragraphIntrinsics = paragraphIntrinsics,
+                constraints = ParagraphConstraints(fontSizeInPx * text.length)
+            )
 
             assertThat(paragraph.maxIntrinsicWidth, equalTo(paragraphIntrinsics.maxIntrinsicWidth))
             assertThat(paragraph.width, equalTo(fontSizeInPx * text.length))
@@ -3309,7 +3342,8 @@
         textStyle: TextStyle? = null,
         density: Density? = null,
         textDirectionAlgorithm: TextDirectionAlgorithm? = null,
-        layoutDirection: LayoutDirection = LayoutDirection.Ltr
+        layoutDirection: LayoutDirection = LayoutDirection.Ltr,
+        constraints: ParagraphConstraints
     ): Paragraph {
         return Paragraph(
             text = text,
@@ -3326,6 +3360,7 @@
                 lineHeight = lineHeight
             ),
             maxLines = maxLines,
+            constraints = constraints,
             density = density ?: defaultDensity,
             layoutDirection = layoutDirection,
             resourceLoader = TestFontResourceLoader(context)
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/AndroidParagraphTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/AndroidParagraphTest.kt
index 072ce79..2645a7c 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/AndroidParagraphTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/AndroidParagraphTest.kt
@@ -94,12 +94,11 @@
                     textStyle = TextStyle(
                         fontSize = fontSize,
                         fontFamily = fontFamily
-                    )
+                    ),
+                    // 2 chars width
+                    constraints = ParagraphConstraints(width = 2 * fontSize.toPx().value)
                 )
 
-                // 2 chars width
-                paragraphAndroid.layout(ParagraphConstraints(width = 2 * fontSize.toPx().value))
-
                 val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
                 textPaint.textSize = fontSize.toPx().value
                 textPaint.typeface = TypefaceAdapter().create(fontFamily)
@@ -123,9 +122,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, hasSpan(ForegroundColorSpan::class, 0, text.length))
     }
@@ -137,9 +136,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, hasSpan(ForegroundColorSpan::class, 0, "abc".length))
     }
@@ -155,9 +154,9 @@
             textStyles = listOf(
                 AnnotatedString.Item(textStyle, 0, text.length),
                 AnnotatedString.Item(textStyleOverwrite, 0, "abc".length)
-            )
+            ),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, hasSpan(ForegroundColorSpan::class, 0, text.length))
         assertThat(paragraph.charSequence, hasSpan(ForegroundColorSpan::class, 0, "abc".length))
@@ -174,9 +173,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence, hasSpan(StrikethroughSpan::class, 0, text.length))
@@ -189,9 +188,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence, hasSpan(UnderlineSpan::class, 0, text.length))
@@ -204,9 +203,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence, hasSpan(StrikethroughSpan::class, 0, "abc".length))
@@ -219,9 +218,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence, hasSpan(UnderlineSpan::class, 0, "abc".length))
@@ -238,9 +237,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence, hasSpan(UnderlineSpan::class, 0, "abc".length))
@@ -257,9 +256,9 @@
 
             val paragraph = simpleParagraph(
                 text = text,
-                textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+                textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             assertThat(paragraph.charSequence, hasSpan(AbsoluteSizeSpan::class, 0, text.length))
         }
@@ -275,9 +274,9 @@
 
             val paragraph = simpleParagraph(
                 text = text,
-                textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+                textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             assertThat(paragraph.charSequence, hasSpan(AbsoluteSizeSpan::class, 0, "abc".length))
         }
@@ -298,9 +297,9 @@
                 textStyles = listOf(
                     AnnotatedString.Item(textStyle, 0, text.length),
                     AnnotatedString.Item(textStyleOverwrite, 0, "abc".length)
-                )
+                ),
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             assertThat(paragraph.charSequence, hasSpan(AbsoluteSizeSpan::class, 0, text.length))
             assertThat(paragraph.charSequence, hasSpan(AbsoluteSizeSpan::class, 0, "abc".length))
@@ -319,9 +318,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(
             paragraph.charSequence,
@@ -339,9 +338,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(
             paragraph.charSequence,
@@ -359,9 +358,10 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
+
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence, hasSpan(LetterSpacingSpan::class, 0, text.length))
     }
@@ -373,9 +373,10 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
+
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence, hasSpan(LetterSpacingSpan::class, 0, "abc".length))
     }
@@ -391,9 +392,10 @@
             textStyles = listOf(
                 AnnotatedString.Item(textStyle, 0, text.length),
                 AnnotatedString.Item(textStyleOverwrite, 0, "abc".length)
-            )
+            ),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
+
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence, hasSpan(LetterSpacingSpan::class, 0, text.length))
         assertThat(paragraph.charSequence, hasSpan(LetterSpacingSpan::class, 0, "abc".length))
@@ -411,9 +413,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence,
@@ -431,9 +433,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence,
@@ -456,9 +458,9 @@
             textStyles = listOf(
                 AnnotatedString.Item(textStyle, 0, text.length),
                 AnnotatedString.Item(textStyleOverwrite, 0, "abc".length)
-            )
+            ),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(paragraph.charSequence,
@@ -487,9 +489,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, hasSpan(LocaleSpan::class, 0, text.length))
     }
@@ -502,9 +504,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, hasSpan(LocaleSpan::class, 0, "abc".length))
     }
@@ -520,9 +522,9 @@
             textStyles = listOf(
                 AnnotatedString.Item(textStyle, 0, text.length),
                 AnnotatedString.Item(textStyleOverwrite, 0, "abc".length)
-            )
+            ),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, hasSpan(LocaleSpan::class, 0, text.length))
         assertThat(paragraph.charSequence, hasSpan(LocaleSpan::class, 0, "abc".length))
@@ -539,10 +541,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, hasSpan(BaselineShiftSpan::class, 0, text.length))
     }
@@ -554,10 +555,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, hasSpan(BaselineShiftSpan::class, 0, "abc".length))
     }
@@ -574,10 +574,9 @@
             textStyles = listOf(
                 AnnotatedString.Item(textStyle, 0, text.length),
                 AnnotatedString.Item(textStyleOverwrite, 0, "abc".length)
-            )
+            ),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, hasSpan(BaselineShiftSpan::class, 0, text.length))
         assertThat(paragraph.charSequence, hasSpan(BaselineShiftSpan::class, 0, "abc".length))
@@ -599,10 +598,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence, not(hasSpan(ScaleXSpan::class, 0, text.length)))
         assertThat(paragraph.charSequence, not(hasSpan(SkewXSpan::class, 0, text.length)))
@@ -621,10 +619,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(
             paragraph.charSequence,
@@ -646,10 +643,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length)),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(
             paragraph.charSequence,
@@ -666,10 +662,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textIndent = TextIndent(firstLine.px, restLine.px)
+            textIndent = TextIndent(firstLine.px, restLine.px),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(
             paragraph.charSequence,
@@ -691,10 +686,9 @@
             text = text,
             textStyles = listOf(
                 AnnotatedString.Item(textStyle, start = 0, end = text.length)
-            )
+            ),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(
             paragraph.charSequence,
@@ -727,10 +721,9 @@
             textStyles = listOf(
                 AnnotatedString.Item(textStyle, start = 0, end = text.length),
                 AnnotatedString.Item(textStyleOverwrite, start = 0, end = "abc".length)
-            )
+            ),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(
             paragraph.charSequence,
@@ -776,9 +769,9 @@
                     expectedStart,
                     expectedEnd
                 )
-            )
+            ),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(
@@ -814,9 +807,9 @@
                     expectedStart,
                     expectedEnd
                 )
-            )
+            ),
+            constraints = ParagraphConstraints(width = 100.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(paragraph.charSequence.toString(), equalTo(text))
         assertThat(
@@ -834,10 +827,9 @@
 
         val paragraph = simpleParagraph(
             text = text,
-            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length))
+            textStyles = listOf(AnnotatedString.Item(textStyle, 0, "abc".length)),
+            constraints = ParagraphConstraints(width = 100.0f) // width is not important
         )
-        // width is not important
-        paragraph.layout(ParagraphConstraints(width = 100.0f))
 
         assertThat(
             paragraph.charSequence,
@@ -851,9 +843,9 @@
         val typefaceAdapter = mock<TypefaceAdapter>()
         val paragraph = simpleParagraph(
             text = "abc",
-            typefaceAdapter = typefaceAdapter
+            typefaceAdapter = typefaceAdapter,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         verify(typefaceAdapter, never()).create(
             fontFamily = any(),
@@ -874,9 +866,9 @@
                 fontFamily = null,
                 fontWeight = FontWeight.bold
             ),
-            typefaceAdapter = typefaceAdapter
+            typefaceAdapter = typefaceAdapter,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         verify(typefaceAdapter, times(1)).create(
             fontFamily = eq(null),
@@ -900,9 +892,9 @@
                 fontFamily = null,
                 fontStyle = FontStyle.Italic
             ),
-            typefaceAdapter = typefaceAdapter
+            typefaceAdapter = typefaceAdapter,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         verify(typefaceAdapter, times(1)).create(
             fontFamily = eq(null),
@@ -927,9 +919,9 @@
             textStyle = TextStyle(
                 fontFamily = fontFamily
             ),
-            typefaceAdapter = typefaceAdapter
+            typefaceAdapter = typefaceAdapter,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         verify(typefaceAdapter, times(1)).create(
             fontFamily = eq(fontFamily),
@@ -952,9 +944,9 @@
             textStyle = TextStyle(
                 fontFamily = fontFamily
             ),
-            typefaceAdapter = typefaceAdapter
+            typefaceAdapter = typefaceAdapter,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
 
         verify(typefaceAdapter, times(1)).create(
             fontFamily = eq(fontFamily),
@@ -979,9 +971,10 @@
                     fontFamily = fontFamily,
                     fontSize = fontSize
                 ),
-                ellipsis = true
+                ellipsis = true,
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
+
             for (i in 0 until paragraph.lineCount) {
                 assertFalse(paragraph.isEllipsisApplied(i))
             }
@@ -1002,9 +995,9 @@
                 textStyle = TextStyle(
                     fontFamily = fontFamily,
                     fontSize = fontSize
-                )
+                ),
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             assertTrue(paragraph.isEllipsisApplied(0))
         }
@@ -1024,9 +1017,9 @@
                 textStyle = TextStyle(
                     fontFamily = fontFamily,
                     fontSize = fontSize
-                )
+                ),
+                constraints = ParagraphConstraints(width = paragraphWidth)
             )
-            paragraph.layout(ParagraphConstraints(width = paragraphWidth))
 
             for (i in 0 until paragraph.lineCount) {
                 assertFalse(paragraph.isEllipsisApplied(i))
@@ -1040,9 +1033,9 @@
             val fontSize = 100.sp
             val paragraph = simpleParagraph(
                 text = "",
-                textStyle = TextStyle(fontSize = fontSize)
+                textStyle = TextStyle(fontSize = fontSize),
+                constraints = ParagraphConstraints(width = 0.0f)
             )
-            paragraph.layout(ParagraphConstraints(width = 0.0f))
 
             assertThat(paragraph.textPaint.textSize, equalTo(fontSize.toPx().value))
         }
@@ -1058,9 +1051,9 @@
                 textStyle = TextStyle(
                     fontSize = fontSize,
                     fontSizeScale = fontSizeScale
-                )
+                ),
+                constraints = ParagraphConstraints(width = 0.0f)
             )
-            paragraph.layout(ParagraphConstraints(width = 0.0f))
 
             assertThat(paragraph.textPaint.textSize, equalTo(fontSize.toPx().value * fontSizeScale))
         }
@@ -1073,9 +1066,9 @@
 
         val paragraph = simpleParagraph(
             text = "",
-            textStyle = TextStyle(localeList = localeList)
+            textStyle = TextStyle(localeList = localeList),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(paragraph.textPaint.textLocale.language, equalTo(platformLocale.language))
         assertThat(paragraph.textPaint.textLocale.country, equalTo(platformLocale.country))
@@ -1086,9 +1079,9 @@
         val color = Color(0x12345678)
         val paragraph = simpleParagraph(
             text = "",
-            textStyle = TextStyle(color = color)
+            textStyle = TextStyle(color = color),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(paragraph.textPaint.color, equalTo(color.toArgb()))
     }
@@ -1098,9 +1091,9 @@
         val letterSpacing = 2.0f
         val paragraph = simpleParagraph(
             text = "",
-            textStyle = TextStyle(letterSpacing = letterSpacing)
+            textStyle = TextStyle(letterSpacing = letterSpacing),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(paragraph.textPaint.letterSpacing, equalTo(letterSpacing))
     }
@@ -1110,9 +1103,9 @@
         val fontFeatureSettings = "\"kern\" 0"
         val paragraph = simpleParagraph(
             text = "",
-            textStyle = TextStyle(fontFeatureSettings = fontFeatureSettings)
+            textStyle = TextStyle(fontFeatureSettings = fontFeatureSettings),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(paragraph.textPaint.fontFeatureSettings, equalTo(fontFeatureSettings))
     }
@@ -1126,9 +1119,9 @@
                 textGeometricTransform = TextGeometricTransform(
                     scaleX = scaleX
                 )
-            )
+            ),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(paragraph.textPaint.textScaleX, equalTo(scaleX))
     }
@@ -1142,9 +1135,9 @@
                 textGeometricTransform = TextGeometricTransform(
                     skewX = skewX
                 )
-            )
+            ),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(paragraph.textPaint.textSkewX, equalTo(skewX))
     }
@@ -1153,9 +1146,9 @@
     fun testTextStyle_decoration_underline_appliedOnTextPaint() {
         val paragraph = simpleParagraph(
             text = "",
-            textStyle = TextStyle(decoration = TextDecoration.Underline)
+            textStyle = TextStyle(decoration = TextDecoration.Underline),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(paragraph.textPaint.isUnderlineText, equalTo(true))
     }
@@ -1164,9 +1157,9 @@
     fun testTextStyle_decoration_lineThrough_appliedOnTextPaint() {
         val paragraph = simpleParagraph(
             text = "",
-            textStyle = TextStyle(decoration = TextDecoration.LineThrough)
+            textStyle = TextStyle(decoration = TextDecoration.LineThrough),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(paragraph.textPaint.isStrikeThruText, equalTo(true))
     }
@@ -1179,9 +1172,9 @@
         val color = Color(0x12345678)
         val paragraph = simpleParagraph(
             text = text,
-            textStyle = TextStyle(background = color)
+            textStyle = TextStyle(background = color),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(paragraph.charSequence,
             hasSpan(BackgroundColorSpan::class, 0, text.length) { span ->
@@ -1198,9 +1191,9 @@
         val baselineShift = BaselineShift.Subscript
         val paragraph = simpleParagraph(
             text = text,
-            textStyle = TextStyle(baselineShift = baselineShift)
+            textStyle = TextStyle(baselineShift = baselineShift),
+            constraints = ParagraphConstraints(width = 0.0f)
         )
-        paragraph.layout(ParagraphConstraints(width = 0.0f))
 
         assertThat(
             paragraph.charSequence,
@@ -1213,9 +1206,10 @@
     @Test
     fun locale_isDefaultLocaleIfNotProvided() {
         val text = "abc"
-        val paragraph = simpleParagraph(text = text)
-
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+        val paragraph = simpleParagraph(
+            text = text,
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
+        )
 
         assertThat(
             paragraph.textLocale.toLanguageTag(),
@@ -1229,11 +1223,10 @@
         val text = "abc"
         val paragraph = simpleParagraph(
             text = text,
-            textStyle = TextStyle(localeList = localeList)
+            textStyle = TextStyle(localeList = localeList),
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
-
         assertThat(paragraph.textLocale.toLanguageTag(), equalTo("en-US"))
     }
 
@@ -1243,11 +1236,10 @@
         val text = "abc"
         val paragraph = simpleParagraph(
             text = text,
-            textStyle = TextStyle(localeList = localeList)
+            textStyle = TextStyle(localeList = localeList),
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
-
         assertThat(paragraph.textLocale.toLanguageTag(), equalTo("ja-JP"))
     }
 
@@ -1257,11 +1249,10 @@
         val text = "abc"
         val paragraph = simpleParagraph(
             text = text,
-            textStyle = TextStyle(localeList = localeList)
+            textStyle = TextStyle(localeList = localeList),
+            constraints = ParagraphConstraints(width = Float.MAX_VALUE)
         )
 
-        paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
-
         assertThat(paragraph.textLocale.toLanguageTag(), equalTo("ja"))
     }
 
@@ -1357,8 +1348,11 @@
     @Test
     fun floatingWidth() {
         val floatWidth = 1.3f
-        val paragraph = simpleParagraph(text = "Hello, World")
-        paragraph.layout(ParagraphConstraints(floatWidth))
+        val paragraph = simpleParagraph(
+            text = "Hello, World",
+            constraints = ParagraphConstraints(floatWidth)
+        )
+
         assertEquals(floatWidth, paragraph.width)
     }
 
@@ -1369,6 +1363,7 @@
         textAlign: TextAlign? = null,
         ellipsis: Boolean? = null,
         maxLines: Int? = null,
+        constraints: ParagraphConstraints,
         textStyle: TextStyle? = null,
         layoutDirection: LayoutDirection = LayoutDirection.Ltr,
         typefaceAdapter: TypefaceAdapter = TypefaceAdapter()
@@ -1384,6 +1379,7 @@
             ),
             maxLines = maxLines,
             ellipsis = ellipsis,
+            constraints = constraints,
             density = Density(density = 1f),
             layoutDirection = layoutDirection
         )
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
index d876743..5a2bfee 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
@@ -30,22 +30,41 @@
 import kotlin.math.max
 
 /**
- * The class that renders multiple paragraphs at once.
+ * Lays out and renders multiple paragraphs at once. Unlike [Paragraph], supports multiple
+ * [ParagraphStyle]s in a given text.
  *
- * It's designed to support multiple [ParagraphStyle]s in single text widget.
+ * @param intrinsics previously calculated text intrinsics
+ * @param maxLines the maximum number of lines that the text can have
+ * @param ellipsis whether to ellipsize text, applied only when [maxLines] is set
  */
 internal class MultiParagraph(
     val intrinsics: MultiParagraphIntrinsics,
     val maxLines: Int? = null,
-    ellipsis: Boolean? = null
+    ellipsis: Boolean? = null,
+    constraints: ParagraphConstraints
 ) {
 
+    /**
+     *  Lays out a given [annotatedString] with the given constraints. Unlike a [Paragraph],
+     *  [MultiParagraph] can handle a text what has multiple paragraph styles.
+     *
+     * @param annotatedString the text to be laid out
+     * @param textStyle the [TextStyle] to be applied to the whole text
+     * @param paragraphStyle the [ParagraphStyle] to be applied to the whole text
+     * @param maxLines the maximum number of lines that the text can have
+     * @param ellipsis whether to ellipsize text, applied only when [maxLines] is set
+     * @param constraints how wide the text is allowed to be
+     * @param density density of the device
+     * @param layoutDirection the layout direction of the widget
+     * @param resourceLoader [Font.ResourceLoader] to be used to load the font given in [TextStyle]s
+     */
     constructor(
         annotatedString: AnnotatedString,
         textStyle: TextStyle = TextStyle(),
         paragraphStyle: ParagraphStyle = ParagraphStyle(),
         maxLines: Int? = null,
         ellipsis: Boolean? = null,
+        constraints: ParagraphConstraints,
         density: Density,
         layoutDirection: LayoutDirection,
         resourceLoader: Font.ResourceLoader
@@ -59,7 +78,8 @@
             resourceLoader = resourceLoader
         ),
         maxLines = maxLines,
-        ellipsis = ellipsis
+        ellipsis = ellipsis,
+        constraints = constraints
     )
 
     private val annotatedString get() = intrinsics.annotatedString
@@ -89,36 +109,19 @@
      *
      * See the discussion of the `maxLines` and `ellipsis` arguments at [ParagraphStyle].
      */
-    var didExceedMaxLines: Boolean = false
-        private set
-        get() {
-            assertNeedLayout()
-            return field
-        }
+    val didExceedMaxLines: Boolean
 
     /**
      * The amount of horizontal space this paragraph occupies.
-     *
-     * Valid only after [layout] has been called.
      */
-    var width: Float = 0f
-        private set
-        get() {
-            assertNeedLayout()
-            return field
-        }
+    val width: Float
 
     /**
      * The amount of vertical space this paragraph occupies.
      *
      * Valid only after [layout] has been called.
      */
-    var height: Float = 0f
-        private set
-        get() {
-            assertNeedLayout()
-            return field
-        }
+    val height: Float
 
     /**
      * The distance from the top of the paragraph to the alphabetic
@@ -126,7 +129,6 @@
      */
     val firstBaseline: Float
         get() {
-            assertNeedLayout()
             return if (paragraphInfoList.isEmpty()) {
                 0f
             } else {
@@ -140,7 +142,6 @@
      */
     val lastBaseline: Float
         get() {
-            assertNeedLayout()
             return if (paragraphInfoList.isEmpty()) {
                 0f
             } else {
@@ -149,47 +150,32 @@
         }
 
     /** The total number of lines in the text. */
-    var lineCount: Int = 0
-        private set
-        get() {
-            assertNeedLayout()
-            return field
-        }
-
-    private var needLayout = true
+    val lineCount: Int
 
     private val paragraphInfoList: List<ParagraphInfo>
 
     init {
+        // create sub paragraphs and layouts
         this.paragraphInfoList = intrinsics.infoList.map {
             ParagraphInfo(
                 paragraph = Paragraph(
                     it.intrinsics,
                     maxLines,
-                    ellipsis
+                    ellipsis,
+                    constraints
                 ),
                 startIndex = it.startIndex,
                 endIndex = it.endIndex
             )
         }
-    }
 
-    /**
-     * Computes the size and position of each glyph in the paragraph.
-     *
-     * The [ParagraphConstraints] control how wide the text is allowed to be.
-     */
-    fun layout(constraints: ParagraphConstraints) {
-        this.needLayout = false
-        this.width = constraints.width
-        this.didExceedMaxLines = false
-
+        // final layout
+        var didExceedMaxLines = false
         var currentLineCount = 0
         var currentHeight = 0f
 
         for ((index, paragraphInfo) in paragraphInfoList.withIndex()) {
             val paragraph = paragraphInfo.paragraph
-            paragraph.layout(constraints)
 
             paragraphInfo.startLineIndex = currentLineCount
             paragraphInfo.endLineIndex = currentLineCount + paragraph.lineCount
@@ -204,18 +190,18 @@
             if (paragraph.didExceedMaxLines ||
                 (currentLineCount == maxLines && index != this.paragraphInfoList.lastIndex)
             ) {
-                this.didExceedMaxLines = true
+                didExceedMaxLines = true
                 break
             }
         }
+        this.didExceedMaxLines = didExceedMaxLines
         this.lineCount = currentLineCount
         this.height = currentHeight
+        this.width = constraints.width
     }
 
     /** Paint the paragraphs to canvas. */
     fun paint(canvas: Canvas) {
-        assertNeedLayout()
-
         canvas.save()
         paragraphInfoList.forEach {
             it.paragraph.paint(canvas)
@@ -232,7 +218,6 @@
                         " or start > end!"
             )
         }
-        assertNeedLayout()
 
         if (start == end) return Path()
 
@@ -257,7 +242,6 @@
 
     /** Returns the character offset closest to the given graphical position. */
     fun getOffsetForPosition(position: PxPosition): Int {
-        assertNeedLayout()
         val paragraphIndex = when {
             position.y.value <= 0f -> 0
             position.y.value >= height -> paragraphInfoList.lastIndex
@@ -277,7 +261,6 @@
      * includes the top, bottom, left and right of a character.
      */
     fun getBoundingBox(offset: Int): Rect {
-        assertNeedLayout()
         assertIndexInRange(offset)
 
         val paragraphIndex = findParagraphByIndex(paragraphInfoList, offset)
@@ -288,7 +271,6 @@
 
     /** Get the primary horizontal position for the specified text offset. */
     fun getPrimaryHorizontal(offset: Int): Float {
-        assertNeedLayout()
         if (offset !in 0..annotatedString.text.length) {
             throw AssertionError("offset($offset) is out of bounds " +
                     "(0,${annotatedString.text.length}")
@@ -307,7 +289,6 @@
 
     /** Get the secondary horizontal position for the specified text offset. */
     fun getSecondaryHorizontal(offset: Int): Float {
-        assertNeedLayout()
         if (offset !in 0..annotatedString.text.length) {
             throw AssertionError("offset($offset) is out of bounds " +
                     "(0,${annotatedString.text.length}")
@@ -328,7 +309,6 @@
      * Get the text direction of the paragraph containing the given offset.
      */
     fun getParagraphDirection(offset: Int): TextDirection {
-        assertNeedLayout()
         if (offset !in 0..annotatedString.text.length) {
             throw AssertionError("offset($offset) is out of bounds " +
                     "(0,${annotatedString.text.length}")
@@ -349,7 +329,6 @@
      * Get the text direction of the character at the given offset.
      */
     fun getBidiRunDirection(offset: Int): TextDirection {
-        assertNeedLayout()
         if (offset !in 0..annotatedString.text.length) {
             throw AssertionError("offset($offset) is out of bounds " +
                     "(0,${annotatedString.text.length}")
@@ -374,7 +353,6 @@
      * http://www.unicode.org/reports/tr29/#Word_Boundaries
      */
     fun getWordBoundary(offset: Int): TextRange {
-        assertNeedLayout()
         assertIndexInRange(offset)
 
         val paragraphIndex = findParagraphByIndex(paragraphInfoList, offset)
@@ -386,7 +364,6 @@
 
     /** Returns rectangle of the cursor area. */
     fun getCursorRect(offset: Int): Rect {
-        assertNeedLayout()
         if (offset !in 0..annotatedString.text.length) {
             throw AssertionError("offset($offset) is out of bounds " +
                     "(0,${annotatedString.text.length}")
@@ -409,7 +386,6 @@
      * beyond the end of the text, you get the last line.
      */
     fun getLineForOffset(offset: Int): Int {
-        assertNeedLayout()
         assertIndexInRange(offset)
 
         val paragraphIndex = findParagraphByIndex(paragraphInfoList, offset)
@@ -420,7 +396,6 @@
 
     /** Returns the left x Coordinate of the given line. */
     fun getLineLeft(lineIndex: Int): Float {
-        assertNeedLayout()
         assertLineIndexInRange(lineIndex)
 
         val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
@@ -432,7 +407,6 @@
 
     /** Returns the right x Coordinate of the given line. */
     fun getLineRight(lineIndex: Int): Float {
-        assertNeedLayout()
         assertLineIndexInRange(lineIndex)
 
         val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
@@ -444,7 +418,6 @@
 
     /** Returns the bottom y coordinate of the given line. */
     fun getLineBottom(lineIndex: Int): Float {
-        assertNeedLayout()
         assertLineIndexInRange(lineIndex)
 
         val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
@@ -456,7 +429,6 @@
 
     /** Returns the height of the given line. */
     fun getLineHeight(lineIndex: Int): Float {
-        assertNeedLayout()
         assertLineIndexInRange(lineIndex)
 
         val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
@@ -468,7 +440,6 @@
 
     /** Returns the width of the given line. */
     fun getLineWidth(lineIndex: Int): Float {
-        assertNeedLayout()
         assertLineIndexInRange(lineIndex)
 
         val paragraphIndex = findParagraphByLineIndex(paragraphInfoList, lineIndex)
@@ -478,12 +449,6 @@
         }
     }
 
-    private fun assertNeedLayout() {
-        if (needLayout) {
-            throw IllegalStateException("")
-        }
-    }
-
     private fun assertIndexInRange(offset: Int) {
         if (offset !in (0 until annotatedString.text.length)) {
             throw IndexOutOfBoundsException("offset($offset) is out of bounds" +
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt
index 7a80e90..1f40bbf 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt
@@ -29,10 +29,7 @@
 import androidx.ui.text.style.TextDirection
 
 /**
- * A paragraph of text.
- *
- * A paragraph retains the size and position of each glyph in the text and can
- * be efficiently resized and painted.
+ * A paragraph of text that is laid out.
  *
  * Paragraphs can be displayed on a [Canvas] using the [paint] method.
  */
@@ -97,13 +94,6 @@
      */
     val lineCount: Int
 
-    /**
-     * Computes the size and position of each glyph in the paragraph.
-     *
-     * The [ParagraphConstraints] control how wide the text is allowed to be.
-     */
-    fun layout(constraints: ParagraphConstraints)
-
     /** Returns path that enclose the given text range. */
     fun getPathForRange(start: Int, end: Int): Path
 
@@ -118,10 +108,8 @@
 
     /**
      * Returns the bottom y coordinate of the given line.
-     *
-     * @hide
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    // TODO(qqd) add tests
     fun getLineBottom(lineIndex: Int): Float
 
     /** Returns the height of the given line. */
@@ -192,7 +180,19 @@
 }
 
 /**
- * @see Paragraph
+ * Lays out a given [text] with the given constraints. A paragraph is a text that has a single
+ * [ParagraphStyle].
+ *
+ * @param text the text to be laid out
+ * @param style the [TextStyle] to be applied to the whole text
+ * @param paragraphStyle the [ParagraphStyle] to be applied to the whole text
+ * @param textStyles [TextStyle]s to be applied to parts of text
+ * @param maxLines the maximum number of lines that the text can have
+ * @param ellipsis whether to ellipsize text, applied only when [maxLines] is set
+ * @param constraints how wide the text is allowed to be
+ * @param density density of the device
+ * @param layoutDirection the layout direction of the widget
+ * @param resourceLoader [Font.ResourceLoader] to be used to load the font given in [TextStyle]s
  */
 /* actual */ fun Paragraph(
     text: String,
@@ -201,6 +201,7 @@
     textStyles: List<AnnotatedString.Item<TextStyle>>,
     maxLines: Int? = null,
     ellipsis: Boolean? = null,
+    constraints: ParagraphConstraints,
     density: Density,
     layoutDirection: LayoutDirection,
     resourceLoader: Font.ResourceLoader
@@ -212,6 +213,7 @@
         textStyles = textStyles,
         maxLines = maxLines,
         ellipsis = ellipsis,
+        constraints = constraints,
         typefaceAdapter = TypefaceAdapter(
             resourceLoader = resourceLoader
         ),
@@ -220,14 +222,25 @@
     )
 }
 
+/**
+ * Lays out a given [text] with the given constraints. A paragraph is a text that has a single
+ * [ParagraphStyle].
+ *
+ * @param paragraphIntrinsics [ParagraphIntrinsics] instance
+ * @param maxLines the maximum number of lines that the text can have
+ * @param ellipsis whether to ellipsize text, applied only when [maxLines] is set
+ * @param constraints how wide the text is allowed to be
+ */
 /* actual */ fun Paragraph(
     paragraphIntrinsics: ParagraphIntrinsics,
     maxLines: Int? = null,
-    ellipsis: Boolean? = null
+    ellipsis: Boolean? = null,
+    constraints: ParagraphConstraints
 ): Paragraph {
     return AndroidParagraph(
         paragraphIntrinsics = paragraphIntrinsics as AndroidParagraphIntrinsics,
         maxLines = maxLines,
-        ellipsis = ellipsis
+        ellipsis = ellipsis,
+        constraints = constraints
     )
 }
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt
index be60281..dec867f 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt
@@ -18,7 +18,7 @@
 /**
  * Layout constraints for [Paragraph] objects.
  *
- * Instances of this class are typically used with [Paragraph.layout].
+ * Instances of this class are typically used with [Paragraph].
  *
  * The only constraint that can be specified is the [width]. See the discussion
  * at [width] for more details.
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt
index 836fc41..029ae98 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphStyle.kt
@@ -46,7 +46,7 @@
             }
         }
     }
-    // TODO(siyamed) uncomment
+
     /**
      * Returns a new paragraph style that is a combination of this style and the given [other]
      * style.
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/TextDelegate.kt b/ui/ui-text/src/main/java/androidx/ui/text/TextDelegate.kt
index 73edbde..8046682 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/TextDelegate.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/TextDelegate.kt
@@ -247,11 +247,7 @@
      * while still being greater than or equal to `minWidth` and less than or equal to `maxWidth`.
      */
     private fun layoutText(minWidth: Float, maxWidth: Float): MultiParagraph {
-        val multiParagraph = MultiParagraph(
-            intrinsics = layoutIntrinsics(),
-            maxLines = maxLines,
-            ellipsis = overflow == TextOverflow.Ellipsis
-        )
+        val paragraphIntrinsics = layoutIntrinsics()
 
         // if minWidth == maxWidth the width is fixed.
         //    therefore we can pass that value to our paragraph and use it
@@ -265,12 +261,15 @@
         val width = if (minWidth == maxWidth) {
             maxWidth
         } else {
-            multiParagraph.maxIntrinsicWidth.coerceIn(minWidth, maxWidth)
+            paragraphIntrinsics.maxIntrinsicWidth.coerceIn(minWidth, maxWidth)
         }
 
-        multiParagraph.layout(ParagraphConstraints(width = width))
-
-        return multiParagraph
+        return MultiParagraph(
+            intrinsics = paragraphIntrinsics,
+            maxLines = maxLines,
+            ellipsis = overflow == TextOverflow.Ellipsis,
+            constraints = ParagraphConstraints(width = width)
+        )
     }
 
     fun layout(constraints: Constraints) {
@@ -475,9 +474,9 @@
             textStyles = listOf(),
             density = density,
             resourceLoader = resourceLoader,
-            layoutDirection = layoutDirection
+            layoutDirection = layoutDirection,
+            constraints = ParagraphConstraints(Float.POSITIVE_INFINITY)
         )
-        paragraph.layout(ParagraphConstraints(Float.POSITIVE_INFINITY))
 
         val fadeWidth = paragraph.maxIntrinsicWidth
         val fadeHeight = paragraph.height
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt
index 6751ec75..539a20c 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt
@@ -34,7 +34,6 @@
 import androidx.ui.core.LayoutDirection
 import androidx.ui.core.PxPosition
 import androidx.ui.engine.geometry.Rect
-import androidx.ui.graphics.toArgb
 import androidx.ui.graphics.Canvas
 import androidx.ui.graphics.Path
 import androidx.ui.text.AnnotatedString
@@ -53,7 +52,8 @@
 internal class AndroidParagraph constructor(
     val paragraphIntrinsics: AndroidParagraphIntrinsics,
     val maxLines: Int?,
-    val ellipsis: Boolean?
+    val ellipsis: Boolean?,
+    val constraints: ParagraphConstraints
 ) : Paragraph {
 
     constructor(
@@ -63,6 +63,7 @@
         textStyles: List<AnnotatedString.Item<TextStyle>>,
         maxLines: Int?,
         ellipsis: Boolean?,
+        constraints: ParagraphConstraints,
         typefaceAdapter: TypefaceAdapter,
         density: Density,
         layoutDirection: LayoutDirection
@@ -77,19 +78,51 @@
             layoutDirection = layoutDirection
         ),
         maxLines = maxLines,
-        ellipsis = ellipsis
+        ellipsis = ellipsis,
+        constraints = constraints
     )
 
-    /**
-     * Initialized when [layout] function is called.
-     */
-    private var layout: TextLayout? = null
+    private val layout: TextLayout
 
-    override var width: Float = 0.0f
-        get() = ensureLayout.let { field }
+    override val width: Float
+
+    init {
+        val paragraphStyle = paragraphIntrinsics.paragraphStyle
+
+        val alignment = toLayoutAlign(paragraphStyle.textAlign)
+
+        val maxLines = maxLines ?: DEFAULT_MAX_LINES
+        val justificationMode = when (paragraphStyle.textAlign) {
+            TextAlign.Justify -> JUSTIFICATION_MODE_INTER_WORD
+            else -> DEFAULT_JUSTIFICATION_MODE
+        }
+
+        val lineSpacingMultiplier = paragraphStyle.lineHeight ?: DEFAULT_LINESPACING_MULTIPLIER
+
+        val ellipsize = if (ellipsis == true) {
+            TextUtils.TruncateAt.END
+        } else {
+            null
+        }
+
+        this.width = constraints.width
+
+        layout = TextLayout(
+            charSequence = paragraphIntrinsics.charSequence,
+            width = constraints.width,
+            textPaint = textPaint,
+            ellipsize = ellipsize,
+            alignment = alignment,
+            textDirectionHeuristic = paragraphIntrinsics.textDirectionHeuristic,
+            lineSpacingMultiplier = lineSpacingMultiplier,
+            maxLines = maxLines,
+            justificationMode = justificationMode,
+            layoutIntrinsics = paragraphIntrinsics.layoutIntrinsics
+        )
+    }
 
     override val height: Float
-        get() = ensureLayout.let {
+        get() = layout.let {
             // TODO(Migration/haoyuchang): Figure out a way to add bottomPadding properly
             val lineCount = it.lineCount
             if (maxLines != null &&
@@ -109,31 +142,24 @@
         get() = paragraphIntrinsics.minIntrinsicWidth
 
     override val firstBaseline: Float
-        get() = ensureLayout.getLineBaseline(0)
+        get() = layout.getLineBaseline(0)
 
     override val lastBaseline: Float
-        get() = if (maxLines != null && maxLines >= 0 && maxLines < lineCount) {
-            ensureLayout.getLineBaseline(maxLines - 1)
+        get() = if (maxLines != null && maxLines in 0 until lineCount) {
+            layout.getLineBaseline(maxLines - 1)
         } else {
-            ensureLayout.getLineBaseline(lineCount - 1)
+            layout.getLineBaseline(lineCount - 1)
         }
 
     override val didExceedMaxLines: Boolean
-        get() = ensureLayout.didExceedMaxLines
+        get() = layout.didExceedMaxLines
 
     @VisibleForTesting
     internal val textLocale: JavaLocale
         get() = paragraphIntrinsics.textPaint.textLocale
 
     override val lineCount: Int
-        get() = ensureLayout.lineCount
-
-    private val ensureLayout: TextLayout
-        get() {
-            return this.layout ?: throw java.lang.IllegalStateException(
-                "layout() should be called first"
-            )
-        }
+        get() = layout.lineCount
 
     @VisibleForTesting
     internal val charSequence: CharSequence
@@ -143,43 +169,9 @@
     internal val textPaint: TextPaint
         get() = paragraphIntrinsics.textPaint
 
-    override fun layout(constraints: ParagraphConstraints) {
-        val paragraphStyle = paragraphIntrinsics.paragraphStyle
-
-        val alignment = toLayoutAlign(paragraphStyle.textAlign)
-
-        val maxLines = maxLines ?: DEFAULT_MAX_LINES
-        val justificationMode = when (paragraphStyle.textAlign) {
-            TextAlign.Justify -> JUSTIFICATION_MODE_INTER_WORD
-            else -> DEFAULT_JUSTIFICATION_MODE
-        }
-
-        val lineSpacingMultiplier = paragraphStyle.lineHeight ?: DEFAULT_LINESPACING_MULTIPLIER
-
-        val ellipsize = if (ellipsis == true) {
-            TextUtils.TruncateAt.END
-        } else {
-            null
-        }
-
-        layout = TextLayout(
-            charSequence = paragraphIntrinsics.charSequence,
-            width = constraints.width,
-            textPaint = textPaint,
-            ellipsize = ellipsize,
-            alignment = alignment,
-            textDirectionHeuristic = paragraphIntrinsics.textDirectionHeuristic,
-            lineSpacingMultiplier = lineSpacingMultiplier,
-            maxLines = maxLines,
-            justificationMode = justificationMode,
-            layoutIntrinsics = paragraphIntrinsics.layoutIntrinsics
-        )
-        this.width = constraints.width
-    }
-
     override fun getOffsetForPosition(position: PxPosition): Int {
-        val line = ensureLayout.getLineForVertical(position.y.value.toInt())
-        return ensureLayout.getOffsetForHorizontal(line, position.x.value)
+        val line = layout.getLineForVertical(position.y.value.toInt())
+        return layout.getOffsetForHorizontal(line, position.x.value)
     }
 
     /**
@@ -188,12 +180,12 @@
      */
     // TODO:(qqd) Implement RTL case.
     override fun getBoundingBox(offset: Int): Rect {
-        val left = ensureLayout.getPrimaryHorizontal(offset)
-        val right = ensureLayout.getPrimaryHorizontal(offset + 1)
+        val left = layout.getPrimaryHorizontal(offset)
+        val right = layout.getPrimaryHorizontal(offset + 1)
 
-        val line = ensureLayout.getLineForOffset(offset)
-        val top = ensureLayout.getLineTop(line)
-        val bottom = ensureLayout.getLineBottom(line)
+        val line = layout.getLineForOffset(offset)
+        val top = layout.getLineTop(line)
+        val bottom = layout.getLineBottom(line)
 
         return Rect(top = top, bottom = bottom, left = left, right = right)
     }
@@ -206,7 +198,7 @@
             )
         }
         val path = android.graphics.Path()
-        ensureLayout.getSelectionPath(start, end, path)
+        layout.getSelectionPath(start, end, path)
         return Path(path)
     }
 
@@ -216,7 +208,6 @@
         }
         // TODO(nona): Support cursor drawable.
         val cursorWidth = 4.0f
-        val layout = ensureLayout
         val horizontal = layout.getPrimaryHorizontal(offset)
         val line = layout.getLineForOffset(offset)
 
@@ -229,46 +220,46 @@
     }
 
     private val wordBoundary: WordBoundary by lazy {
-        WordBoundary(textLocale, ensureLayout.text)
+        WordBoundary(textLocale, layout.text)
     }
 
     override fun getWordBoundary(offset: Int): TextRange {
         return TextRange(wordBoundary.getWordStart(offset), wordBoundary.getWordEnd(offset))
     }
 
-    override fun getLineLeft(lineIndex: Int): Float = ensureLayout.getLineLeft(lineIndex)
+    override fun getLineLeft(lineIndex: Int): Float = layout.getLineLeft(lineIndex)
 
-    override fun getLineRight(lineIndex: Int): Float = ensureLayout.getLineRight(lineIndex)
+    override fun getLineRight(lineIndex: Int): Float = layout.getLineRight(lineIndex)
 
-    override fun getLineBottom(lineIndex: Int): Float = ensureLayout.getLineBottom(lineIndex)
+    override fun getLineBottom(lineIndex: Int): Float = layout.getLineBottom(lineIndex)
 
-    override fun getLineHeight(lineIndex: Int): Float = ensureLayout.getLineHeight(lineIndex)
+    override fun getLineHeight(lineIndex: Int): Float = layout.getLineHeight(lineIndex)
 
-    override fun getLineWidth(lineIndex: Int): Float = ensureLayout.getLineWidth(lineIndex)
+    override fun getLineWidth(lineIndex: Int): Float = layout.getLineWidth(lineIndex)
 
-    override fun getLineForOffset(offset: Int): Int = ensureLayout.getLineForOffset(offset)
+    override fun getLineForOffset(offset: Int): Int = layout.getLineForOffset(offset)
 
     override fun getPrimaryHorizontal(offset: Int): Float =
-        ensureLayout.getPrimaryHorizontal(offset)
+        layout.getPrimaryHorizontal(offset)
 
     override fun getSecondaryHorizontal(offset: Int): Float =
-        ensureLayout.getSecondaryHorizontal(offset)
+        layout.getSecondaryHorizontal(offset)
 
     override fun getParagraphDirection(offset: Int): TextDirection {
-        val lineIndex = ensureLayout.getLineForOffset(offset)
-        val direction = ensureLayout.getParagraphDirection(lineIndex)
+        val lineIndex = layout.getLineForOffset(offset)
+        val direction = layout.getParagraphDirection(lineIndex)
         return if (direction == 1) TextDirection.Ltr else TextDirection.Rtl
     }
 
     override fun getBidiRunDirection(offset: Int): TextDirection {
-        return if (ensureLayout.isRtlCharAt(offset)) TextDirection.Rtl else TextDirection.Ltr
+        return if (layout.isRtlCharAt(offset)) TextDirection.Rtl else TextDirection.Ltr
     }
 
     /**
      * @return true if the given line is ellipsized, else false.
      */
     internal fun isEllipsisApplied(lineIndex: Int): Boolean =
-        ensureLayout.isEllipsisApplied(lineIndex)
+        layout.isEllipsisApplied(lineIndex)
 
     override fun paint(canvas: Canvas) {
         val nativeCanvas = canvas.nativeCanvas
@@ -276,7 +267,7 @@
             nativeCanvas.save()
             nativeCanvas.clipRect(0f, 0f, width, height)
         }
-        ensureLayout.paint(nativeCanvas)
+        layout.paint(nativeCanvas)
         if (didExceedMaxLines) {
             nativeCanvas.restore()
         }
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatForceDarkTest.java b/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatForceDarkTest.java
index d1c6ca5..f41446b 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatForceDarkTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatForceDarkTest.java
@@ -21,6 +21,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.util.Base64;
 import android.view.ViewGroup;
 import android.webkit.WebView;
 
@@ -30,6 +31,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,13 +41,34 @@
 
 @RunWith(AndroidJUnit4.class)
 public class WebSettingsCompatForceDarkTest {
+    private final String mNoDarkThemeSupport = Base64.encodeToString((
+                      "<html>\n"
+                    + "  <head>"
+                    + "  </head>"
+                    + "  <body>"
+                    + "  </body>"
+                    + "</html>").getBytes(), Base64.NO_PADDING);
+    private final String mDarkThemeSupport = Base64.encodeToString((
+                      "<html>"
+                    + "  <head>"
+                    + "    <meta name=\"color-scheme\" content=\"light dark\">"
+                    + "    <style>"
+                    + "      @media (prefers-color-scheme: dark) {"
+                    + "      body {background-color: green; }"
+                    + "    </style>"
+                    + "  </head>"
+                    + "  <body>"
+                    + "  </body>"
+                    + "</html>"
+
+    ).getBytes(), Base64.NO_PADDING);
+
     // LayoutParams are null until WebView has a parent Activity.
     // Test testForceDark_rendersDark requires LayoutParams to define
     // width and height of WebView to capture its bitmap representation.
     @Rule
     public final ActivityTestRule<WebViewTestActivity> mActivityRule =
             new ActivityTestRule<>(WebViewTestActivity.class);
-
     private WebViewOnUiThread mWebViewOnUiThread;
 
     @Before
@@ -85,8 +108,6 @@
     public void testForceDark_rendersDark() throws Throwable {
         WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK);
         setWebViewSize(64, 64);
-        Map<Integer, Integer> histogram;
-        Integer[] colourValues;
 
         // Loading about:blank into a force-dark-on webview should result in a dark background
         WebSettingsCompat.setForceDark(
@@ -96,10 +117,7 @@
                 WebSettingsCompat.FORCE_DARK_ON);
 
         mWebViewOnUiThread.loadUrlAndWaitForCompletion("about:blank");
-        histogram = getBitmapHistogram(mWebViewOnUiThread.captureBitmap(), 0, 0, 64, 64);
-        assertEquals("Bitmap should have a single colour", histogram.size(), 1);
-        colourValues = histogram.keySet().toArray(new Integer[0]);
-        assertTrue("Bitmap colour should be dark", Color.luminance(colourValues[0]) < 0.5f);
+        assertTrue("Bitmap colour should be dark", Color.luminance(getWebPageColor()) < 0.5f);
 
         // Loading about:blank into a force-dark-off webview should result in a light background
         WebSettingsCompat.setForceDark(
@@ -109,12 +127,91 @@
                 WebSettingsCompat.FORCE_DARK_OFF);
 
         mWebViewOnUiThread.loadUrlAndWaitForCompletion("about:blank");
-        histogram = getBitmapHistogram(
-                mWebViewOnUiThread.captureBitmap(), 0, 0, 64, 64);
-        assertEquals("Bitmap should have a single colour", histogram.size(), 1);
-        colourValues = histogram.keySet().toArray(new Integer[0]);
         assertTrue("Bitmap colour should be light",
-                Color.luminance(colourValues[0]) > 0.5f);
+                Color.luminance(getWebPageColor()) > 0.5f);
+    }
+
+    /**
+     * Test to exercise USER_AGENT_DARKENING_ONLY option,
+     * i.e. web contents are always darkened by a user agent.
+     */
+    @Test
+    @SmallTest
+    public void testForceDark_userAgentDarkeningOnly() {
+        WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK);
+        WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK_STRATEGY);
+        setWebViewSize(64, 64);
+
+        // Loading empty page with or without dark theme support into a force-dark-on webview with
+        // force dark only algorithm should result in a dark background.
+        WebSettingsCompat.setForceDark(
+                mWebViewOnUiThread.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+        WebSettingsCompat.setForceDarkStrategy(mWebViewOnUiThread.getSettings(),
+                WebSettingsCompat.USER_AGENT_DARKENING_ONLY);
+
+        mWebViewOnUiThread.loadDataAndWaitForCompletion(mNoDarkThemeSupport, "text/html", "base64");
+        assertTrue("Bitmap colour should be dark", Color.luminance(getWebPageColor()) < 0.5f);
+
+        mWebViewOnUiThread.loadDataAndWaitForCompletion(mDarkThemeSupport, "text/html", "base64");
+        assertTrue("Bitmap colour should be dark", Color.luminance(getWebPageColor()) < 0.5f);
+    }
+
+    /**
+     * Test to exercise WEB_THEME_DARKENING_ONLY option,
+     * i.e. web contents are darkened only by web theme.
+     */
+    // TODO(amalova): Enable test when meta-tag is supported by WV
+    @Test
+    @SmallTest
+    @Ignore
+    public void testForceDark_webThemeDarkeningOnly() {
+        WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK);
+        WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK_STRATEGY);
+        setWebViewSize(64, 64);
+
+        WebSettingsCompat.setForceDark(
+                mWebViewOnUiThread.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+        WebSettingsCompat.setForceDarkStrategy(mWebViewOnUiThread.getSettings(),
+                WebSettingsCompat.WEB_THEME_DARKENING_ONLY);
+
+        // Loading a page without dark-theme support should result in a light background as web
+        // page is not darken by a user agent
+        mWebViewOnUiThread.loadDataAndWaitForCompletion(mNoDarkThemeSupport, "text/html", "base64");
+        assertTrue("Bitmap colour should be light", Color.luminance(getWebPageColor()) > 0.5f);
+
+        // Loading a page with dark-theme support should result in a green background (as
+        // specified in media-query)
+        mWebViewOnUiThread.loadDataAndWaitForCompletion(mDarkThemeSupport, "text/html", "base64");
+        assertTrue("Bitmap colour should be green", Color.GREEN  == getWebPageColor());
+    }
+
+    /**
+     * Test to exercise PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING option,
+     * i.e. web contents are darkened by a user agent if there is no dark web theme.
+     */
+    // TODO(amalova): Enable test when meta-tag is supported by WV
+    @Test
+    @SmallTest
+    @Ignore
+    public void testForceDark_preferWebThemeOverUADarkening() {
+        WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK);
+        WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK_STRATEGY);
+        setWebViewSize(64, 64);
+
+        WebSettingsCompat.setForceDark(
+                mWebViewOnUiThread.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+        WebSettingsCompat.setForceDarkStrategy(mWebViewOnUiThread.getSettings(),
+                WebSettingsCompat.PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING);
+
+        // Loading a page without dark-theme support should result in a dark background as
+        // web page is darken by a user agent
+        mWebViewOnUiThread.loadDataAndWaitForCompletion(mNoDarkThemeSupport, "text/html", "base64");
+        assertTrue("Bitmap colour should be dark", Color.luminance(getWebPageColor()) < 0.5f);
+
+        // Loading a page with dark-theme support should result in a green background (as
+        // specified in media-query)
+        mWebViewOnUiThread.loadDataAndWaitForCompletion(mDarkThemeSupport, "text/html", "base64");
+        assertTrue("Bitmap colour should be green", Color.GREEN == getWebPageColor());
     }
 
     private void setWebViewSize(final int width, final int height) {
@@ -127,6 +224,17 @@
         });
     }
 
+    private int getWebPageColor() {
+        Map<Integer, Integer> histogram;
+        Integer[] colourValues;
+
+        histogram = getBitmapHistogram(mWebViewOnUiThread.captureBitmap(), 0, 0, 64, 64);
+        assertEquals("Bitmap should have a single colour", histogram.size(), 1);
+        colourValues = histogram.keySet().toArray(new Integer[0]);
+
+        return colourValues[0];
+    }
+
     private Map<Integer, Integer> getBitmapHistogram(
             Bitmap bitmap, int x, int y, int width, int height) {
         Map<Integer, Integer> histogram = new HashMap<>();
diff --git a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index 560e5fa..209eceb 100644
--- a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -27,6 +27,8 @@
 import androidx.webkit.internal.WebViewFeatureInternal;
 import androidx.webkit.internal.WebViewGlueCommunicator;
 
+import org.chromium.support_lib_boundary.WebSettingsBoundaryInterface;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -381,6 +383,114 @@
         }
     }
 
+    /**
+     * In this mode WebView content will be darkened by a user agent and it will ignore the
+     * web page's dark theme if it exists.
+     *
+     * @see #setForceDarkStrategy
+     * TODO(amalova): unhide
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final int USER_AGENT_DARKENING_ONLY =
+            WebSettingsBoundaryInterface.ForceDarkBehavior.FORCE_DARK_ONLY;
+
+    /**
+     * In this mode WebView content will always be darkened using dark theme provided by web page.
+     * If web page does not provide dark theme support WebView content will be rendered with a
+     * default theme.
+     *
+     * @see #setForceDarkStrategy
+     * TODO(amalova): unhide
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final int WEB_THEME_DARKENING_ONLY =
+            WebSettingsBoundaryInterface.ForceDarkBehavior.MEDIA_QUERY_ONLY;
+
+    /**
+     * In this mode WebView content will be darkened by a user agent unless web page supports dark
+     * theme.
+     *
+     * @see #setForceDarkStrategy
+     * TODO(amalova): unhide
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final int PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING =
+            WebSettingsBoundaryInterface.ForceDarkBehavior.PREFER_MEDIA_QUERY_OVER_FORCE_DARK;
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef(value = {
+            USER_AGENT_DARKENING_ONLY,
+            WEB_THEME_DARKENING_ONLY,
+            PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.PARAMETER, ElementType.METHOD})
+    public @interface ForceDarkStrategy {}
+
+    /**
+     * Set how WebView content should be darkened.
+     *
+     * <p>
+     * This method should only be called if
+     * {@link WebViewFeature#isFeatureSupported(String)}
+     * returns true for {@link WebViewFeature#FORCE_DARK_STRATEGY}.
+     *
+     * @param forceDarkBehavior
+     *
+     * TODO(amalova): unhide
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @SuppressLint("NewApi")
+    @RequiresFeature(name = WebViewFeature.FORCE_DARK_STRATEGY,
+            enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
+    public static void setForceDarkStrategy(@NonNull WebSettings settings,
+            @ForceDarkStrategy int forceDarkBehavior) {
+        WebViewFeatureInternal webViewFeature =
+                WebViewFeatureInternal.getFeature(WebViewFeature.FORCE_DARK_STRATEGY);
+        if (webViewFeature.isSupportedByWebView()) {
+            getAdapter(settings).setForceDarkStrategy(forceDarkBehavior);
+        } else {
+            throw WebViewFeatureInternal.getUnsupportedOperationException();
+        }
+    }
+
+    /**
+     * Get how content is darkened for this WebView.
+     *
+     * <p>
+     * The default force dark mode is {@link #PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING}
+     *
+     * <p>
+     * This method should only be called if
+     * {@link WebViewFeature#isFeatureSupported(String)}
+     * returns true for {@link WebViewFeature#FORCE_DARK_STRATEGY}.
+     *
+     * @return todo
+     *
+     * TODO(amalova): unhide
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @SuppressLint("NewApi")
+    @RequiresFeature(name = WebViewFeature.FORCE_DARK_STRATEGY,
+            enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
+    public static @ForceDarkStrategy int getForceDarkStrategy(@NonNull WebSettings settings) {
+        WebViewFeatureInternal webViewFeature =
+                WebViewFeatureInternal.getFeature(WebViewFeature.FORCE_DARK_STRATEGY);
+        if (webViewFeature.isSupportedByWebView()) {
+            return getAdapter(settings).getForceDark();
+        } else {
+            throw WebViewFeatureInternal.getUnsupportedOperationException();
+        }
+    }
+
     private static WebSettingsAdapter getAdapter(WebSettings settings) {
         return WebViewGlueCommunicator.getCompatConverter().convertSettings(settings);
     }
diff --git a/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index d518787..e32efe2 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -89,6 +89,7 @@
             SUPPRESS_ERROR_PAGE,
             MULTI_PROCESS_QUERY,
             FORCE_DARK,
+            FORCE_DARK_STRATEGY,
     })
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.PARAMETER, ElementType.METHOD})
@@ -413,6 +414,18 @@
     public static final String FORCE_DARK = "FORCE_DARK";
 
     /**
+     * Feature for {@link #isFeatureSupported(String)}.
+     * This feature covers
+     * {@link WebSettingsCompat#setForceDarkStrategy(WebSettings, int)} and
+     * {@link WebSettingsCompat#getForceDarkStrategy(WebSettings)}.
+     *
+     * TODO(amalova): unhide
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
+
+    /**
      * Return whether a feature is supported at run-time. On devices running Android version {@link
      * android.os.Build.VERSION_CODES#LOLLIPOP} and higher, this will check whether a feature is
      * supported, depending on the combination of the desired feature, the Android version of
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
index 36ca0a1..ab0df4b 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
@@ -99,4 +99,18 @@
     public int getForceDark() {
         return mBoundaryInterface.getForceDark();
     }
+
+    /**
+     * Adapter method for {@link androidx.webkit.WebSettingsCompat#setForceDarkStrategy}.
+     */
+    public void setForceDarkStrategy(int forceDarkStrategy) {
+        mBoundaryInterface.setForceDarkBehavior(forceDarkStrategy);
+    }
+
+    /**
+     * Adapter method for {@link androidx.webkit.WebSettingsCompat#getForceDarkStrategy}.
+     */
+    public int getForceDarkStrategy() {
+        return mBoundaryInterface.getForceDarkBehavior();
+    }
 }
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java b/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
index b460ecb..0e7589d 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
@@ -339,6 +339,13 @@
      */
     FORCE_DARK(WebViewFeature.FORCE_DARK, Features.FORCE_DARK),
 
+    /**
+     * This feature covers
+     * {@link androidx.webkit.WebSettingsCompat#setForceDarkStrategy(WebSettings, int)} and
+     * {@link androidx.webkit.WebSettingsCompat#getForceDarkStrategy(WebSettings)}.
+     */
+    FORCE_DARK_STRATEGY(WebViewFeature.FORCE_DARK_STRATEGY, Features.FORCE_DARK_BEHAVIOR),
+
     ;  // This semicolon ends the enum. Add new features with a trailing comma above this line.
 
     private static final int NOT_SUPPORTED_BY_FRAMEWORK = -1;
diff --git a/work/workmanager-foreground/build.gradle b/work/workmanager-foreground/build.gradle
deleted file mode 100644
index 5bce41a..0000000
--- a/work/workmanager-foreground/build.gradle
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-
-import androidx.build.LibraryGroups
-import androidx.build.LibraryVersions
-import androidx.build.AndroidXExtension
-
-import static androidx.build.dependencies.DependenciesKt.*
-import androidx.build.Publish
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("kotlin-android")
-}
-
-android {
-    buildTypes {
-        debug {
-            // Breaks Kotlin compiler
-            testCoverageEnabled = false
-        }
-    }
-}
-
-dependencies {
-    api project(':work:work-runtime')
-    androidTestImplementation(project(":work:work-runtime-ktx"))
-    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
-    androidTestImplementation(ANDROIDX_TEST_CORE)
-    androidTestImplementation(ANDROIDX_TEST_RUNNER)
-    androidTestImplementation(ESPRESSO_CORE)
-    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
-    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
-    testImplementation(JUNIT)
-}
-
-androidx {
-    name = "Android WorkManager Foreground Service Support"
-    publish = Publish.NONE
-    mavenVersion = LibraryVersions.WORK
-    mavenGroup = LibraryGroups.WORK
-    inceptionYear = "2019"
-    description = "Android WorkManager Foreground Service Support"
-    url = AndroidXExtension.ARCHITECTURE_URL
-    failOnDeprecationWarnings = false
-}