[go: nahoru, domu]

Autofill Interface and Implementation

Bug: 138548805
Test: Included new tests in this CL
Change-Id: I240e6d0b7a01719c097cea9438c0f5a88feddac3
diff --git a/ui/ui-core/api/1.0.0-alpha01.txt b/ui/ui-core/api/1.0.0-alpha01.txt
index 1648b3a..fb0c76f 100644
--- a/ui/ui-core/api/1.0.0-alpha01.txt
+++ b/ui/ui-core/api/1.0.0-alpha01.txt
@@ -31,6 +31,42 @@
 
 }
 
+package androidx.ui.autofill {
+
+  public interface Autofill {
+    method public void cancelAutofillForNode(androidx.ui.autofill.AutofillNode autofillNode);
+    method public void requestAutofillForNode(androidx.ui.autofill.AutofillNode autofillNode);
+  }
+
+  public final class AutofillNode {
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public java.util.List<androidx.ui.autofill.AutofillType> component1();
+    method public android.graphics.Rect? component2();
+    method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
+    method public android.graphics.Rect? getBoundingBox();
+    method public int getId();
+    method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
+    method public void setBoundingBox(android.graphics.Rect? p);
+    property public final int id;
+  }
+
+  public final class AutofillTree {
+    ctor public AutofillTree();
+    method public java.util.Map<java.lang.Integer,androidx.ui.autofill.AutofillNode> getChildren();
+    method public kotlin.Unit? performAutofill(int id, String value);
+    method public operator void plusAssign(androidx.ui.autofill.AutofillNode autofillNode);
+    property public final java.util.Map<java.lang.Integer,androidx.ui.autofill.AutofillNode> children;
+  }
+
+  public enum AutofillType {
+    enum_constant public static final androidx.ui.autofill.AutofillType EmailAddress;
+    enum_constant public static final androidx.ui.autofill.AutofillType Name;
+  }
+
+}
+
 package androidx.ui.core {
 
   public final class Bounds {
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index 1648b3a..fb0c76f 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -31,6 +31,42 @@
 
 }
 
+package androidx.ui.autofill {
+
+  public interface Autofill {
+    method public void cancelAutofillForNode(androidx.ui.autofill.AutofillNode autofillNode);
+    method public void requestAutofillForNode(androidx.ui.autofill.AutofillNode autofillNode);
+  }
+
+  public final class AutofillNode {
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public java.util.List<androidx.ui.autofill.AutofillType> component1();
+    method public android.graphics.Rect? component2();
+    method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
+    method public android.graphics.Rect? getBoundingBox();
+    method public int getId();
+    method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
+    method public void setBoundingBox(android.graphics.Rect? p);
+    property public final int id;
+  }
+
+  public final class AutofillTree {
+    ctor public AutofillTree();
+    method public java.util.Map<java.lang.Integer,androidx.ui.autofill.AutofillNode> getChildren();
+    method public kotlin.Unit? performAutofill(int id, String value);
+    method public operator void plusAssign(androidx.ui.autofill.AutofillNode autofillNode);
+    property public final java.util.Map<java.lang.Integer,androidx.ui.autofill.AutofillNode> children;
+  }
+
+  public enum AutofillType {
+    enum_constant public static final androidx.ui.autofill.AutofillType EmailAddress;
+    enum_constant public static final androidx.ui.autofill.AutofillType Name;
+  }
+
+}
+
 package androidx.ui.core {
 
   public final class Bounds {
diff --git a/ui/ui-core/api/restricted_1.0.0-alpha01.txt b/ui/ui-core/api/restricted_1.0.0-alpha01.txt
index 1648b3a..fb0c76f 100644
--- a/ui/ui-core/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-core/api/restricted_1.0.0-alpha01.txt
@@ -31,6 +31,42 @@
 
 }
 
+package androidx.ui.autofill {
+
+  public interface Autofill {
+    method public void cancelAutofillForNode(androidx.ui.autofill.AutofillNode autofillNode);
+    method public void requestAutofillForNode(androidx.ui.autofill.AutofillNode autofillNode);
+  }
+
+  public final class AutofillNode {
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public java.util.List<androidx.ui.autofill.AutofillType> component1();
+    method public android.graphics.Rect? component2();
+    method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
+    method public android.graphics.Rect? getBoundingBox();
+    method public int getId();
+    method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
+    method public void setBoundingBox(android.graphics.Rect? p);
+    property public final int id;
+  }
+
+  public final class AutofillTree {
+    ctor public AutofillTree();
+    method public java.util.Map<java.lang.Integer,androidx.ui.autofill.AutofillNode> getChildren();
+    method public kotlin.Unit? performAutofill(int id, String value);
+    method public operator void plusAssign(androidx.ui.autofill.AutofillNode autofillNode);
+    property public final java.util.Map<java.lang.Integer,androidx.ui.autofill.AutofillNode> children;
+  }
+
+  public enum AutofillType {
+    enum_constant public static final androidx.ui.autofill.AutofillType EmailAddress;
+    enum_constant public static final androidx.ui.autofill.AutofillType Name;
+  }
+
+}
+
 package androidx.ui.core {
 
   public final class Bounds {
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 1648b3a..fb0c76f 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -31,6 +31,42 @@
 
 }
 
+package androidx.ui.autofill {
+
+  public interface Autofill {
+    method public void cancelAutofillForNode(androidx.ui.autofill.AutofillNode autofillNode);
+    method public void requestAutofillForNode(androidx.ui.autofill.AutofillNode autofillNode);
+  }
+
+  public final class AutofillNode {
+    ctor public AutofillNode(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public java.util.List<androidx.ui.autofill.AutofillType> component1();
+    method public android.graphics.Rect? component2();
+    method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? component3();
+    method public androidx.ui.autofill.AutofillNode copy(java.util.List<? extends androidx.ui.autofill.AutofillType> autofillTypes, android.graphics.Rect? boundingBox, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit>? onFill);
+    method public java.util.List<androidx.ui.autofill.AutofillType> getAutofillTypes();
+    method public android.graphics.Rect? getBoundingBox();
+    method public int getId();
+    method public kotlin.jvm.functions.Function1<java.lang.String,kotlin.Unit>? getOnFill();
+    method public void setBoundingBox(android.graphics.Rect? p);
+    property public final int id;
+  }
+
+  public final class AutofillTree {
+    ctor public AutofillTree();
+    method public java.util.Map<java.lang.Integer,androidx.ui.autofill.AutofillNode> getChildren();
+    method public kotlin.Unit? performAutofill(int id, String value);
+    method public operator void plusAssign(androidx.ui.autofill.AutofillNode autofillNode);
+    property public final java.util.Map<java.lang.Integer,androidx.ui.autofill.AutofillNode> children;
+  }
+
+  public enum AutofillType {
+    enum_constant public static final androidx.ui.autofill.AutofillType EmailAddress;
+    enum_constant public static final androidx.ui.autofill.AutofillType Name;
+  }
+
+}
+
 package androidx.ui.core {
 
   public final class Bounds {
diff --git a/ui/ui-core/src/main/java/androidx/ui/autofill/Autofill.kt b/ui/ui-core/src/main/java/androidx/ui/autofill/Autofill.kt
new file mode 100644
index 0000000..6e13b2c
--- /dev/null
+++ b/ui/ui-core/src/main/java/androidx/ui/autofill/Autofill.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.autofill
+
+import android.graphics.Rect
+import androidx.annotation.GuardedBy
+
+/**
+ * Autofill API.
+ *
+ * This interface is available to all composables via an ambient. The composable can then request
+ * or cancel autofill as required. For instance, the [TextField] can call [requestAutofillForNode]
+ * when it gains focus, and [cancelAutofillForNode] when it loses focus.
+ */
+interface Autofill {
+
+    /**
+     * Request autofill for the specified node.
+     *
+     * @param autofillNode The node that needs to be autofilled.
+     *
+     * This function is usually called when an autofillable component gains focus.
+     */
+    fun requestAutofillForNode(autofillNode: AutofillNode)
+
+    /**
+     * Cancel a previously supplied autofill request.
+     *
+     * @param autofillNode The node that needs to be autofilled.
+     *
+     * This function is usually called when an autofillable component loses focus.
+     */
+    fun cancelAutofillForNode(autofillNode: AutofillNode)
+}
+
+// TODO(b/138551812): Add more autofill types.
+/** Autofill type information. */
+enum class AutofillType {
+    EmailAddress,
+    Name
+}
+
+/**
+ * Every autofillable composable will have an [AutofillNode]. (An autofill node will be created
+ * for every semantics node that adds autofill properties). This node is used to request/cancel
+ * autofill, and it holds the [onFill] lambda which is called by the autofill framework.
+ *
+ * @property autofillTypes A list of autofill types for this node. These types are conveyed to the
+ * autofill framework and it is used to call [onFill] with the appropriate value. If you don't set
+ * this property, the autofill framework will use heuristics to guess the type. This property is a
+ * list because some fields can have multiple types. For instance, userid in a login form can
+ * either be a username or an email address. TODO(b/138731416): Check with the autofill service
+ * team if the order matters, and how duplicate types are handled.
+ *
+ * @property boundingBox The screen coordinates of the composable being autofilled.
+ * This data is used by the autofill framework to decide where to show the autofill popup.
+ *
+ * @property onFill The callback that is called by the autofill framework to perform autofill.
+ *
+ * @property id A virtual id that is automatically generated for each node.
+ */
+data class AutofillNode(
+    val autofillTypes: List<AutofillType> = listOf(),
+    var boundingBox: Rect? = null,
+    val onFill: ((String) -> Unit)?
+) {
+    internal companion object {
+        @GuardedBy("this")
+        private var previousId = 0
+
+        @Synchronized
+        private fun generateId() = ++previousId
+    }
+
+    val id: Int = generateId()
+}
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/autofill/AutofillTree.kt b/ui/ui-core/src/main/java/androidx/ui/autofill/AutofillTree.kt
new file mode 100644
index 0000000..c3ca40e
--- /dev/null
+++ b/ui/ui-core/src/main/java/androidx/ui/autofill/AutofillTree.kt
@@ -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.
+ */
+
+package androidx.ui.autofill
+
+/**
+ * The autofill tree.
+ *
+ * This is used temporarily until we have a semantic tree implemented (b/138604305). This
+ * implementation is a tree of height = 1, and we don't use the root node right now, so this is
+ * essentially a group of leaf nodes.
+ */
+class AutofillTree {
+    val children: MutableMap<Int, AutofillNode> = mutableMapOf()
+
+    operator fun plusAssign(autofillNode: AutofillNode) {
+        children[autofillNode.id] = autofillNode
+    }
+
+    fun performAutofill(id: Int, value: String) = children[id]?.onFill?.invoke(value)
+}
\ No newline at end of file
diff --git a/ui/ui-core/src/test/java/androidx/ui/autofill/AutofillNodeTest.kt b/ui/ui-core/src/test/java/androidx/ui/autofill/AutofillNodeTest.kt
new file mode 100644
index 0000000..d6afd3e
--- /dev/null
+++ b/ui/ui-core/src/test/java/androidx/ui/autofill/AutofillNodeTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.autofill
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class AutofillNodeTest {
+    @Test
+    fun eachInstanceHasUniqueId() {
+        assertThat(listOf(AutofillNode {}.id, AutofillNode {}.id, AutofillNode {}.id))
+            .containsNoDuplicates()
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/api/1.0.0-alpha01.txt b/ui/ui-framework/api/1.0.0-alpha01.txt
index fbd9ec5..bc84889 100644
--- a/ui/ui-framework/api/1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/1.0.0-alpha01.txt
@@ -210,6 +210,8 @@
     method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void WithDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,kotlin.Unit> block);
     method @CheckResult(suggest="+") public static androidx.compose.Effect<androidx.ui.core.Density> ambientDensity();
+    method public static androidx.compose.Ambient<androidx.ui.autofill.Autofill> getAutofillAmbient();
+    method public static androidx.compose.Ambient<androidx.ui.autofill.AutofillTree> getAutofillTreeAmbient();
     method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
     method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
     method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index fbd9ec5..bc84889 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -210,6 +210,8 @@
     method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void WithDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,kotlin.Unit> block);
     method @CheckResult(suggest="+") public static androidx.compose.Effect<androidx.ui.core.Density> ambientDensity();
+    method public static androidx.compose.Ambient<androidx.ui.autofill.Autofill> getAutofillAmbient();
+    method public static androidx.compose.Ambient<androidx.ui.autofill.AutofillTree> getAutofillTreeAmbient();
     method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
     method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
     method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
diff --git a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
index fbd9ec5..bc84889 100644
--- a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
@@ -210,6 +210,8 @@
     method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void WithDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,kotlin.Unit> block);
     method @CheckResult(suggest="+") public static androidx.compose.Effect<androidx.ui.core.Density> ambientDensity();
+    method public static androidx.compose.Ambient<androidx.ui.autofill.Autofill> getAutofillAmbient();
+    method public static androidx.compose.Ambient<androidx.ui.autofill.AutofillTree> getAutofillTreeAmbient();
     method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
     method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
     method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index fbd9ec5..bc84889 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -210,6 +210,8 @@
     method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
     method public static void WithDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,kotlin.Unit> block);
     method @CheckResult(suggest="+") public static androidx.compose.Effect<androidx.ui.core.Density> ambientDensity();
+    method public static androidx.compose.Ambient<androidx.ui.autofill.Autofill> getAutofillAmbient();
+    method public static androidx.compose.Ambient<androidx.ui.autofill.AutofillTree> getAutofillTreeAmbient();
     method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
     method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
     method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml b/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml
index 77971ae..b88fcee 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml
@@ -27,6 +27,14 @@
                 <category android:name="androidx.ui.demos.SAMPLE_CODE" />
             </intent-filter>
         </activity>
+        <activity android:name=".autofill.ExplicitAutofillTypesActivity"
+                  android:configChanges="orientation|screenSize"
+                  android:label="Semantics/Autofill/Explicit Autofill Types">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="androidx.ui.demos.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
         <activity android:name=".VectorGraphicsActivity"
                   android:configChanges="orientation|screenSize"
                   android:label="Graphics/Vector graphics">
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt
new file mode 100644
index 0000000..3716bfe
--- /dev/null
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.framework.demos.autofill
+
+import android.app.Activity
+import android.graphics.Rect
+import android.os.Bundle
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.ambient
+import androidx.compose.composer
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.autofill.AutofillNode
+import androidx.ui.autofill.AutofillType
+import androidx.ui.core.AutofillAmbient
+import androidx.ui.core.AutofillTreeAmbient
+import androidx.ui.core.EditorStyle
+import androidx.ui.core.TextField
+import androidx.ui.core.Text
+import androidx.ui.material.themeTextStyle
+import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.OnChildPositioned
+import androidx.ui.core.PxPosition
+import androidx.ui.core.dp
+import androidx.ui.core.setContent
+import androidx.ui.input.EditorModel
+import androidx.ui.input.ImeAction
+import androidx.ui.input.KeyboardType
+import androidx.ui.layout.Column
+import androidx.ui.layout.CrossAxisAlignment
+import androidx.ui.layout.HeightSpacer
+import androidx.ui.material.MaterialTheme
+
+class ExplicitAutofillTypesActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            MaterialTheme {
+                Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+
+                    val nameState = +state { EditorModel(text = "Enter name here") }
+                    val emailState = +state { EditorModel(text = "Enter email here") }
+                    val autofill = +ambient(AutofillAmbient)
+                    val labelStyle = +themeTextStyle { subtitle1.copy() }
+                    val textStyle = +themeTextStyle { h6.copy() }
+
+                    Text("Name", style = labelStyle)
+                    Autofill(
+                        autofillTypes = listOf(AutofillType.Name),
+                         nameState.value = EditorModel(it) }
+                    ) { autofillNode ->
+                        TextField(
+                            value = nameState.value,
+                            keyboardType = KeyboardType.Text,
+                            imeAction = ImeAction.Unspecified,
+                             nameState.value = it },
+                             autofill?.requestAutofillForNode(autofillNode) },
+                             autofill?.cancelAutofillForNode(autofillNode) },
+                            editorStyle = EditorStyle(textStyle = textStyle)
+                        )
+                    }
+
+                    HeightSpacer(40.dp)
+
+                    Text("Email", style = labelStyle)
+                    Autofill(
+                        autofillTypes = listOf(AutofillType.EmailAddress),
+                         emailState.value = EditorModel(it) }
+                    ) { autofillNode ->
+                        TextField(
+                            value = emailState.value,
+                            keyboardType = KeyboardType.Text,
+                            imeAction = ImeAction.Unspecified,
+                             emailState.value = it },
+                             autofill?.requestAutofillForNode(autofillNode) },
+                             autofill?.cancelAutofillForNode(autofillNode) },
+                            editorStyle = EditorStyle(textStyle = textStyle)
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+fun Autofill(
+    autofillTypes: List<AutofillType>,
+    onFill: ((String) -> Unit),
+    @Children children: @Composable() (AutofillNode) -> Unit
+) {
+    val autofillNode = AutofillNode( autofillTypes = autofillTypes)
+
+    val autofillTree = +ambient(AutofillTreeAmbient)
+    autofillTree += autofillNode
+
+    OnChildPositioned( autofillNode.boundingBox = it.boundingBox() }) {
+        children(autofillNode)
+    }
+}
+
+private fun LayoutCoordinates.boundingBox() = localToGlobal(PxPosition.Origin).run {
+    Rect(
+        x.value.toInt(),
+        y.value.toInt(),
+        x.value.toInt() + size.width.value.toInt(),
+        y.value.toInt() + size.height.value.toInt()
+    )
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
index ed51529..9d01941 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
@@ -23,7 +23,6 @@
 import androidx.ui.input.TextInputService
 import androidx.compose.Ambient
 import androidx.compose.composer
-import androidx.compose.Children
 import androidx.compose.Composable
 import androidx.compose.Compose
 import androidx.compose.CompositionContext
@@ -35,6 +34,8 @@
 import androidx.compose.memo
 import androidx.compose.onPreCommit
 import androidx.compose.unaryPlus
+import androidx.ui.autofill.Autofill
+import androidx.ui.autofill.AutofillTree
 import androidx.ui.core.text.AndroidFontResourceLoader
 import androidx.ui.text.font.Font
 import kotlinx.coroutines.Dispatchers
@@ -151,16 +152,20 @@
     val focusManager = +memo { FocusManager() }
     ContextAmbient.Provider(value = context) {
         CoroutineContextAmbient.Provider(value = coroutineContext) {
-        DensityAmbient.Provider(value = Density(context)) {
-            FocusManagerAmbient.Provider(value = focusManager) {
-                TextInputServiceAmbient.Provider(value = craneView.textInputService) {
-                    FontLoaderAmbient.Provider(value = AndroidFontResourceLoader(context)) {
-                        content()
+            DensityAmbient.Provider(value = Density(context)) {
+                FocusManagerAmbient.Provider(value = focusManager) {
+                    TextInputServiceAmbient.Provider(value = craneView.textInputService) {
+                        FontLoaderAmbient.Provider(value = AndroidFontResourceLoader(context)) {
+                            AutofillTreeAmbient.Provider(value = craneView.autofillTree) {
+                                AutofillAmbient.Provider(value = craneView.autofill) {
+                                    content()
+                                }
+                            }
+                        }
                     }
                 }
             }
         }
-        }
     }
 }
 
@@ -170,6 +175,11 @@
 
 val CoroutineContextAmbient = Ambient.of<CoroutineContext>()
 
+val AutofillAmbient = Ambient.of<Autofill?>()
+
+// This will ultimately be replaced by Autofill Semantics (b/138604305).
+val AutofillTreeAmbient = Ambient.of<AutofillTree>()
+
 internal val FocusManagerAmbient = Ambient.of<FocusManager>()
 
 internal val TextInputServiceAmbient = Ambient.of<TextInputService?>()
diff --git a/ui/ui-platform/api/1.0.0-alpha01.txt b/ui/ui-platform/api/1.0.0-alpha01.txt
index d667964..36b5783 100644
--- a/ui/ui-platform/api/1.0.0-alpha01.txt
+++ b/ui/ui-platform/api/1.0.0-alpha01.txt
@@ -1,4 +1,12 @@
 // Signature format: 3.0
+package androidx.ui.autofill {
+
+  public final class AndroidAutofillKt {
+    ctor public AndroidAutofillKt();
+  }
+
+}
+
 package androidx.ui.core {
 
   public final class AndroidCraneView extends android.view.ViewGroup implements androidx.ui.core.Owner androidx.ui.core.SemanticsTreeProvider {
@@ -6,6 +14,8 @@
     method public androidx.ui.core.PxPosition calculatePosition();
     method public void callDraw(androidx.ui.painting.Canvas canvas, androidx.ui.core.ComponentNode node, androidx.ui.core.PxSize parentSize, androidx.ui.core.DensityReceiver densityReceiver);
     method public java.util.List<androidx.ui.core.SemanticsTreeNode> getAllSemanticNodes();
+    method public androidx.ui.autofill.Autofill? getAutofill();
+    method public androidx.ui.autofill.AutofillTree getAutofillTree();
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getCommitUnsubscribe();
     method public androidx.ui.core.Constraints getConstraints();
     method public long getMeasureIteration();
@@ -26,6 +36,8 @@
     method public void setCommitUnsubscribe(kotlin.jvm.functions.Function0<kotlin.Unit>? p);
     method public void setConstraints(androidx.ui.core.Constraints p);
     method public void setRef(androidx.ui.core.Ref<androidx.ui.core.AndroidCraneView>? value);
+    property public final androidx.ui.autofill.Autofill? autofill;
+    property public final androidx.ui.autofill.AutofillTree autofillTree;
     property public final kotlin.jvm.functions.Function0<kotlin.Unit>? commitUnsubscribe;
     property public final androidx.ui.core.Constraints constraints;
     property public long measureIteration;
diff --git a/ui/ui-platform/api/current.txt b/ui/ui-platform/api/current.txt
index d667964..36b5783 100644
--- a/ui/ui-platform/api/current.txt
+++ b/ui/ui-platform/api/current.txt
@@ -1,4 +1,12 @@
 // Signature format: 3.0
+package androidx.ui.autofill {
+
+  public final class AndroidAutofillKt {
+    ctor public AndroidAutofillKt();
+  }
+
+}
+
 package androidx.ui.core {
 
   public final class AndroidCraneView extends android.view.ViewGroup implements androidx.ui.core.Owner androidx.ui.core.SemanticsTreeProvider {
@@ -6,6 +14,8 @@
     method public androidx.ui.core.PxPosition calculatePosition();
     method public void callDraw(androidx.ui.painting.Canvas canvas, androidx.ui.core.ComponentNode node, androidx.ui.core.PxSize parentSize, androidx.ui.core.DensityReceiver densityReceiver);
     method public java.util.List<androidx.ui.core.SemanticsTreeNode> getAllSemanticNodes();
+    method public androidx.ui.autofill.Autofill? getAutofill();
+    method public androidx.ui.autofill.AutofillTree getAutofillTree();
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getCommitUnsubscribe();
     method public androidx.ui.core.Constraints getConstraints();
     method public long getMeasureIteration();
@@ -26,6 +36,8 @@
     method public void setCommitUnsubscribe(kotlin.jvm.functions.Function0<kotlin.Unit>? p);
     method public void setConstraints(androidx.ui.core.Constraints p);
     method public void setRef(androidx.ui.core.Ref<androidx.ui.core.AndroidCraneView>? value);
+    property public final androidx.ui.autofill.Autofill? autofill;
+    property public final androidx.ui.autofill.AutofillTree autofillTree;
     property public final kotlin.jvm.functions.Function0<kotlin.Unit>? commitUnsubscribe;
     property public final androidx.ui.core.Constraints constraints;
     property public long measureIteration;
diff --git a/ui/ui-platform/api/restricted_1.0.0-alpha01.txt b/ui/ui-platform/api/restricted_1.0.0-alpha01.txt
index 1e7cf19..1eb20b2 100644
--- a/ui/ui-platform/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-platform/api/restricted_1.0.0-alpha01.txt
@@ -1,4 +1,12 @@
 // Signature format: 3.0
+package androidx.ui.autofill {
+
+  public final class AndroidAutofillKt {
+    ctor public AndroidAutofillKt();
+  }
+
+}
+
 package androidx.ui.core {
 
   public final class AndroidCraneView extends android.view.ViewGroup implements androidx.ui.core.Owner androidx.ui.core.SemanticsTreeProvider {
@@ -6,6 +14,8 @@
     method public androidx.ui.core.PxPosition calculatePosition();
     method public void callDraw(androidx.ui.painting.Canvas canvas, androidx.ui.core.ComponentNode node, androidx.ui.core.PxSize parentSize, androidx.ui.core.DensityReceiver densityReceiver);
     method public java.util.List<androidx.ui.core.SemanticsTreeNode> getAllSemanticNodes();
+    method public androidx.ui.autofill.Autofill? getAutofill();
+    method public androidx.ui.autofill.AutofillTree getAutofillTree();
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getCommitUnsubscribe();
     method public androidx.ui.core.Constraints getConstraints();
     method public long getMeasureIteration();
@@ -26,6 +36,8 @@
     method public void setCommitUnsubscribe(kotlin.jvm.functions.Function0<kotlin.Unit>? p);
     method public void setConstraints(androidx.ui.core.Constraints p);
     method public void setRef(androidx.ui.core.Ref<androidx.ui.core.AndroidCraneView>? value);
+    property public final androidx.ui.autofill.Autofill? autofill;
+    property public final androidx.ui.autofill.AutofillTree autofillTree;
     property public final kotlin.jvm.functions.Function0<kotlin.Unit>? commitUnsubscribe;
     property public final androidx.ui.core.Constraints constraints;
     property public long measureIteration;
diff --git a/ui/ui-platform/api/restricted_current.txt b/ui/ui-platform/api/restricted_current.txt
index 1e7cf19..1eb20b2 100644
--- a/ui/ui-platform/api/restricted_current.txt
+++ b/ui/ui-platform/api/restricted_current.txt
@@ -1,4 +1,12 @@
 // Signature format: 3.0
+package androidx.ui.autofill {
+
+  public final class AndroidAutofillKt {
+    ctor public AndroidAutofillKt();
+  }
+
+}
+
 package androidx.ui.core {
 
   public final class AndroidCraneView extends android.view.ViewGroup implements androidx.ui.core.Owner androidx.ui.core.SemanticsTreeProvider {
@@ -6,6 +14,8 @@
     method public androidx.ui.core.PxPosition calculatePosition();
     method public void callDraw(androidx.ui.painting.Canvas canvas, androidx.ui.core.ComponentNode node, androidx.ui.core.PxSize parentSize, androidx.ui.core.DensityReceiver densityReceiver);
     method public java.util.List<androidx.ui.core.SemanticsTreeNode> getAllSemanticNodes();
+    method public androidx.ui.autofill.Autofill? getAutofill();
+    method public androidx.ui.autofill.AutofillTree getAutofillTree();
     method public kotlin.jvm.functions.Function0<kotlin.Unit>? getCommitUnsubscribe();
     method public androidx.ui.core.Constraints getConstraints();
     method public long getMeasureIteration();
@@ -26,6 +36,8 @@
     method public void setCommitUnsubscribe(kotlin.jvm.functions.Function0<kotlin.Unit>? p);
     method public void setConstraints(androidx.ui.core.Constraints p);
     method public void setRef(androidx.ui.core.Ref<androidx.ui.core.AndroidCraneView>? value);
+    property public final androidx.ui.autofill.Autofill? autofill;
+    property public final androidx.ui.autofill.AutofillTree autofillTree;
     property public final kotlin.jvm.functions.Function0<kotlin.Unit>? commitUnsubscribe;
     property public final androidx.ui.core.Constraints constraints;
     property public long measureIteration;
diff --git a/ui/ui-platform/src/main/java/androidx/ui/autofill/AndroidAutofill.kt b/ui/ui-platform/src/main/java/androidx/ui/autofill/AndroidAutofill.kt
new file mode 100644
index 0000000..ab83357
--- /dev/null
+++ b/ui/ui-platform/src/main/java/androidx/ui/autofill/AndroidAutofill.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.autofill
+
+import android.os.Build
+import android.util.Log
+import android.util.SparseArray
+import android.view.View
+import android.view.ViewStructure
+import android.view.autofill.AutofillManager
+import android.view.autofill.AutofillValue
+import androidx.annotation.RequiresApi
+
+/**
+ * Autofill implementation for Android.
+ *
+ * @param view The parent compose view.
+ * @param autofillTree The autofill tree. This will be replaced by a semantic tree (b/138604305).
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+internal class AndroidAutofill(val view: View, val autofillTree: AutofillTree) : Autofill {
+
+    val autofillManager = view.context.getSystemService(AutofillManager::class.java)
+        ?: error("Autofill service could not be located.")
+
+    init { view.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES }
+
+    override fun requestAutofillForNode(autofillNode: AutofillNode) {
+        // TODO(b/138731416): Find out what happens when notifyViewEntered() is called multiple times
+        // before calling notifyViewExited().
+        autofillManager.notifyViewEntered(
+            view,
+            autofillNode.id,
+            autofillNode.boundingBox ?: error("requestAutofill called before onChildPositioned()")
+        )
+    }
+
+    override fun cancelAutofillForNode(autofillNode: AutofillNode) {
+        autofillManager.notifyViewExited(view, autofillNode.id)
+    }
+}
+
+/**
+ * Populates the view structure with autofill information.
+ *
+ * @param root A reference to the view structure of the parent compose view.
+ *
+ * This function populates the view structure using the information in the { AutofillTree }.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+internal fun AndroidAutofill.populateViewStructure(root: ViewStructure) {
+
+    // Add child nodes. The function returns the index to the first item.
+    var index = root.addChildCount(autofillTree.children.count())
+
+    for ((id, autofillNode) in autofillTree.children) {
+        root.newChild(index)?.apply {
+            setAutofillId(root.autofillId!!, id)
+            setId(id, view.context.packageName, null, null)
+            setAutofillType(View.AUTOFILL_TYPE_TEXT)
+            setAutofillHints(autofillNode.autofillTypes.map {
+                when (it) {
+                    AutofillType.Name -> View.AUTOFILL_HINT_NAME
+                    AutofillType.EmailAddress -> View.AUTOFILL_HINT_EMAIL_ADDRESS
+                    else -> error("Unsupported autofill type")
+                }
+            }.toTypedArray())
+
+            if (autofillNode.boundingBox == null) {
+                // Do we need an exception here? warning? silently ignore? If the boundingbox is
+                // null, the autofill overlay will not be shown.
+                Log.w(
+                    "Autofill Warning",
+                    """Bounding box not set. 
+                        Did you call perform autofillTree before the component was positioned? """
+                )
+            }
+            autofillNode.boundingBox?.run { setDimens(left, top, 0, 0, width(), height()) }
+        }
+        index++
+    }
+}
+
+/**
+ * Triggers onFill() in response to a request from the autofill framework.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+internal fun AndroidAutofill.performAutofill(values: SparseArray<AutofillValue>) {
+    for (index in 0 until values.size()) {
+        val itemId = values.keyAt(index)
+        val value = values[itemId]
+        when {
+            value.isText -> autofillTree.performAutofill(itemId, value.textValue.toString())
+            value.isDate -> TODO("b/138604541: Add onFill() callback for date")
+            value.isList -> TODO("b/138604541: Add onFill() callback for list")
+            value.isToggle -> TODO("b/138604541:  Add onFill() callback for toggle")
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/AndroidOwner.kt b/ui/ui-platform/src/main/java/androidx/ui/core/AndroidOwner.kt
index 53c87df..4a141ad 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/AndroidOwner.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/AndroidOwner.kt
@@ -20,11 +20,14 @@
 import android.graphics.RenderNode
 import android.os.Build
 import android.os.Looper
+import android.util.SparseArray
 import android.view.Choreographer
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewOutlineProvider
+import android.view.ViewStructure
+import android.view.autofill.AutofillValue
 import android.view.inputmethod.EditorInfo
 import android.view.inputmethod.InputConnection
 import androidx.annotation.RestrictTo
@@ -46,6 +49,11 @@
 import kotlin.math.roundToInt
 import java.util.TreeSet
 import androidx.compose.trace
+import androidx.ui.autofill.AndroidAutofill
+import androidx.ui.autofill.Autofill
+import androidx.ui.autofill.AutofillTree
+import androidx.ui.autofill.performAutofill
+import androidx.ui.autofill.populateViewStructure
 
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 class AndroidCraneView constructor(context: Context)
@@ -64,6 +72,10 @@
     // Map from model to LayoutNodes that *only* need layout and not measure
     private val relayoutOnly = ObserverMap<Any, LayoutNode>()
 
+    // TODO: Replace with SemanticsTree.
+    //  This is a temporary hack until we have a semantics tree implemented.
+    val autofillTree = AutofillTree()
+
     // RepaintBoundaryNodes that have had their boundary changed. When using Views,
     // the size/position of a View should change during layout, so this list
     // is kept separate from dirtyRepaintBoundaryNodes.
@@ -85,6 +97,9 @@
     // Used for tracking which nodes a frame read is applied to
     internal var currentNode: ComponentNode? = null
 
+    private val _autofill = if (autofillSupported()) AndroidAutofill(this, autofillTree) else null
+    val autofill: Autofill? get() = _autofill
+
     override var measureIteration: Long = 1L
         private set
 
@@ -474,6 +489,14 @@
         root.detach()
     }
 
+    override fun onProvideAutofillVirtualStructure(structure: ViewStructure?, flags: Int) {
+        if (autofillSupported() && structure != null) _autofill?.populateViewStructure(structure)
+    }
+
+    override fun autofill(values: SparseArray<AutofillValue>) {
+        if (autofillSupported()) _autofill?.performAutofill(values)
+    }
+
     override fun onTouchEvent(event: MotionEvent): Boolean {
         trace("AndroidOwner:onTouch") {
             pointerInputEventProcessor.process(event.toPointerInputEvent())
@@ -561,6 +584,8 @@
             }
         }
     }
+
+    private fun autofillSupported() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
 }
 
 private class ConstraintRange(val min: IntPx, val max: IntPx)
diff --git a/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidAutofillTest.kt b/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidAutofillTest.kt
new file mode 100644
index 0000000..a135080
--- /dev/null
+++ b/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidAutofillTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.autofill
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.View
+import android.view.autofill.AutofillManager
+import androidx.test.filters.SmallTest
+import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ExpectedException
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class AndroidAutofillTest {
+
+    @get:Rule
+    val expectedException = ExpectedException.none()!!
+
+    private lateinit var androidAutofill: AndroidAutofill
+    private lateinit var autofillManager: AutofillManager
+    private lateinit var view: View
+    private val autofillTree = AutofillTree()
+
+    @Before
+    fun setup() {
+        autofillManager = mock()
+
+        val context: Context = mock()
+        whenever(context.getSystemService(eq(AutofillManager::class.java)))
+            .thenReturn(autofillManager)
+
+        view = mock()
+        whenever(view.context).thenReturn(context)
+
+        androidAutofill = AndroidAutofill(view, autofillTree)
+    }
+
+    @Test
+    fun importantForAutofill_is_yes() {
+        verify(view).setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES)
+    }
+
+    @Test
+    fun requestAutofillForNode_calls_notifyViewEntered() {
+        // Arrange.
+        val autofillNode = AutofillNode( boundingBox = Rect(0, 0, 0, 0))
+
+        // Act.
+        androidAutofill.requestAutofillForNode(autofillNode)
+
+        // Assert.
+        verify(autofillManager, times(1))
+            .notifyViewEntered(view, autofillNode.id, autofillNode.boundingBox!!)
+    }
+
+    @Test
+    fun requestAutofillForNode_beforeComposableIsPositioned_throwsError() {
+        // Arrange - Before the composable is positioned, the boundingBox is null.
+        val autofillNode = AutofillNode(>
+
+        // Assert.
+        expectedException.expectMessage("requestAutofill called before onChildPositioned()")
+
+        // Act.
+        androidAutofill.requestAutofillForNode(autofillNode)
+    }
+
+    @Test
+    fun cancelAutofillForNode_calls_notifyViewExited() {
+        // Arrange.
+        val autofillNode = AutofillNode(>
+
+        // Act.
+        androidAutofill.cancelAutofillForNode(autofillNode)
+
+        // Assert.
+        verify(autofillManager, times(1)).notifyViewExited(view, autofillNode.id)
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidFakes.kt b/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidFakes.kt
new file mode 100644
index 0000000..0d539e3
--- /dev/null
+++ b/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidFakes.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.autofill
+
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.os.Bundle
+import android.os.LocaleList
+import android.util.SparseArray
+import android.view.View
+import android.view.ViewStructure
+import android.view.autofill.AutofillId
+import android.view.autofill.AutofillValue
+import com.nhaarman.mockitokotlin2.mock
+
+/**
+ * A fake implementation of { SparseArray } to use in tests.
+ */
+internal class FakeSparseArray : SparseArray<AutofillValue>() {
+
+    private val map = mutableMapOf<Int, AutofillValue>()
+
+    override fun append(key: Int, value: AutofillValue?) {
+        value?.let { map.putIfAbsent(key, it) }
+    }
+
+    override fun size() = map.count()
+
+    override fun keyAt(index: Int) = map.asSequence().elementAt(index).component1()
+
+    override fun get(key: Int) = map[key] ?: error("no element for the specified key")
+}
+
+/**
+ * A fake implementation of { ViewStructure } to use in tests.
+ */
+internal data class FakeViewStructure(
+    var virtualId: Int = 0,
+    var packageName: String? = null,
+    var typeName: String? = null,
+    var entryName: String? = null,
+    var children: MutableList<FakeViewStructure> = mutableListOf(),
+    var bounds: Rect? = null,
+    private val autofillId: AutofillId? = mock(),
+    private var autofillType: Int = View.AUTOFILL_TYPE_NONE,
+    private var autofillHints: Array<out String> = arrayOf()
+) : ViewStructure() {
+
+    override fun getChildCount() = children.count()
+
+    override fun addChildCount(childCount: Int): Int {
+        repeat(childCount) { children.add(FakeViewStructure()) }
+        return children.count() - childCount
+    }
+
+    override fun newChild(index: Int): FakeViewStructure {
+        if (index >= children.count()) error("Call addChildCount() before calling newChild()")
+        return children[index]
+    }
+
+    override fun getAutofillId() = autofillId
+
+    override fun setAutofillId(rootId: AutofillId, virtualId: Int) {
+        this.virtualId = virtualId
+    }
+
+    override fun setId(
+        virtualId: Int,
+        packageName: String?,
+        typeName: String?,
+        entryName: String?
+    ) {
+        this.virtualId = virtualId
+        this.packageName = packageName
+        this.typeName = typeName
+        this.entryName = entryName
+    }
+
+    override fun setAutofillType(autofillType: Int) {
+        this.autofillType = autofillType
+    }
+
+    override fun setAutofillHints(autofillHints: Array<out String>?) {
+        autofillHints?.let { this.autofillHints = it }
+    }
+
+    override fun setDimens(left: Int, top: Int, x: Int, y: Int, width: Int, height: Int) {
+        this.bounds = Rect(left, top, width - left, height - top)
+    }
+
+    override fun equals(other: Any?) = other is FakeViewStructure &&
+            other.virtualId == virtualId &&
+            other.packageName == packageName &&
+            other.typeName == typeName &&
+            other.entryName == entryName &&
+            other.autofillType == autofillType &&
+            other.autofillHints.contentEquals(autofillHints) &&
+            other.bounds.contentEquals(bounds) &&
+            other.children == children
+
+    override fun hashCode() = super.hashCode()
+
+    // Unimplemented methods.
+
+    override fun setOpaque(p0: Boolean) { TODO("not implemented") }
+
+    override fun setHint(p0: CharSequence?) { TODO("not implemented") }
+
+    override fun setElevation(p0: Float) { TODO("not implemented") }
+
+    override fun getText(): CharSequence { TODO("not implemented") }
+
+    override fun setText(p0: CharSequence?) { TODO("not implemented") }
+
+    override fun setText(p0: CharSequence?, p1: Int, p2: Int) { TODO("not implemented") }
+
+    override fun asyncCommit() { TODO("not implemented") }
+
+    override fun setEnabled(p0: Boolean) { TODO("not implemented") }
+
+    override fun setLocaleList(p0: LocaleList?) { TODO("not implemented") }
+
+    override fun setChecked(p0: Boolean) { TODO("not implemented") }
+
+    override fun setContextClickable(p0: Boolean) { TODO("not implemented") }
+
+    override fun setAccessibilityFocused(p0: Boolean) { TODO("not implemented") }
+
+    override fun setAlpha(p0: Float) { TODO("not implemented") }
+
+    override fun setTransformation(p0: Matrix?) { TODO("not implemented") }
+
+    override fun setClassName(p0: String?) { TODO("not implemented") }
+
+    override fun setLongClickable(p0: Boolean) { TODO("not implemented") }
+
+    override fun getHint(): CharSequence { TODO("not implemented") }
+
+    override fun setInputType(p0: Int) { TODO("not implemented") }
+
+    override fun setWebDomain(p0: String?) { TODO("not implemented") }
+
+    override fun setAutofillOptions(p0: Array<out CharSequence>?) { TODO("not implemented") }
+
+    override fun setTextStyle(p0: Float, p1: Int, p2: Int, p3: Int) { TODO("not implemented") }
+
+    override fun setVisibility(p0: Int) { TODO("not implemented") }
+
+    override fun setHtmlInfo(p0: HtmlInfo) { TODO("not implemented") }
+
+    override fun setTextLines(p0: IntArray?, p1: IntArray?) { TODO("not implemented") }
+
+    override fun getExtras(): Bundle { TODO("not implemented") }
+
+    override fun setClickable(p0: Boolean) { TODO("not implemented") }
+
+    override fun newHtmlInfoBuilder(p0: String): HtmlInfo.Builder { TODO("not implemented") }
+
+    override fun getTextSelectionEnd(): Int { TODO("not implemented") }
+
+    override fun setAutofillId(p0: AutofillId) { TODO("not implemented") }
+
+    override fun hasExtras(): Boolean { TODO("not implemented") }
+
+    override fun setActivated(p0: Boolean) { TODO("not implemented") }
+
+    override fun setFocused(p0: Boolean) { TODO("not implemented") }
+
+    override fun getTextSelectionStart(): Int { TODO("not implemented") }
+
+    override fun setChildCount(p0: Int) { TODO("not implemented") }
+
+    override fun setAutofillValue(p0: AutofillValue?) { TODO("not implemented") }
+
+    override fun setContentDescription(p0: CharSequence?) { TODO("not implemented") }
+
+    override fun setFocusable(p0: Boolean) { TODO("not implemented") }
+
+    override fun setCheckable(p0: Boolean) { TODO("not implemented") }
+
+    override fun asyncNewChild(p0: Int): ViewStructure { TODO("not implemented") }
+
+    override fun setSelected(p0: Boolean) { TODO("not implemented") }
+
+    override fun setDataIsSensitive(p0: Boolean) { TODO("not implemented") }
+}
+
+private fun Rect?.contentEquals(other: Rect?) = when {
+    (other == null && this == null) -> true
+    (other == null || this == null) -> false
+    else -> other.left == left &&
+            other.right == right &&
+            other.bottom == bottom &&
+            other.top == top
+}
\ No newline at end of file
diff --git a/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidPerformAutofillTest.kt b/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidPerformAutofillTest.kt
new file mode 100644
index 0000000..6acec9c
--- /dev/null
+++ b/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidPerformAutofillTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.autofill
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.View
+import android.view.autofill.AutofillManager
+import android.view.autofill.AutofillValue
+import androidx.test.filters.SmallTest
+import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class AndroidPerformAutofillTest {
+    private val autofillTree = AutofillTree()
+    private lateinit var androidAutofill: AndroidAutofill
+
+    @Before
+    fun setup() {
+        val autofillManager: AutofillManager = mock()
+
+        val context: Context = mock()
+        whenever(context.getSystemService(eq(AutofillManager::class.java)))
+            .thenReturn(autofillManager)
+
+        val view: View = mock()
+        whenever(view.context).thenReturn(context)
+
+        androidAutofill = AndroidAutofill(view, autofillTree)
+    }
+
+    @Test
+    fun performAutofill_name() {
+        // Arrange.
+        val onFill: (String) -> Unit = mock()
+        val autofillNode = AutofillNode(
+            >
+            autofillTypes = listOf(AutofillType.Name),
+            boundingBox = Rect(0, 0, 0, 0)
+        )
+        autofillTree += autofillNode
+
+        val autofillValue: AutofillValue = mock()
+        whenever(autofillValue.isText).thenReturn(true)
+        whenever(autofillValue.textValue).thenReturn("First Name")
+
+        val autofillValues = FakeSparseArray().apply { append(autofillNode.id, autofillValue) }
+
+        // Act.
+        androidAutofill.performAutofill(autofillValues)
+
+        // Assert.
+        verify(onFill, times(1)).invoke("First Name")
+    }
+
+    @Test
+    fun performAutofill_email() {
+        // Arrange.
+        val onFill: (String) -> Unit = mock()
+        val autofillNode = AutofillNode(
+            >
+            autofillTypes = listOf(AutofillType.EmailAddress),
+            boundingBox = Rect(0, 0, 0, 0)
+        )
+        autofillTree += autofillNode
+
+        val autofillValue: AutofillValue = mock()
+        whenever(autofillValue.isText).thenReturn(true)
+        whenever(autofillValue.textValue).thenReturn("email@google.com")
+
+        val autofillValues = FakeSparseArray().apply { append(autofillNode.id, autofillValue) }
+
+        // Act.
+        androidAutofill.performAutofill(autofillValues)
+
+        // Assert.
+        verify(onFill, times(1)).invoke("email@google.com")
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidPopulateViewStructureTest.kt b/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidPopulateViewStructureTest.kt
new file mode 100644
index 0000000..db8c641
--- /dev/null
+++ b/ui/ui-platform/src/test/java/androidx/ui/autofill/AndroidPopulateViewStructureTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.autofill
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewStructure
+import android.view.autofill.AutofillManager
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class AndroidPopulateViewStructureTest {
+    private val autofillTree = AutofillTree()
+    private lateinit var androidAutofill: AndroidAutofill
+
+    @Before
+    fun setup() {
+        val autofillManager: AutofillManager = mock()
+
+        val context: Context = mock()
+        whenever(context.getSystemService(eq(AutofillManager::class.java)))
+            .thenReturn(autofillManager)
+        whenever(context.packageName).thenReturn("com.google.testpackage")
+
+        val view: View = mock()
+        whenever(view.context).thenReturn(context)
+
+        androidAutofill = AndroidAutofill(view, autofillTree)
+    }
+
+    @Test
+    fun populateViewStructure_emptyAutofillTree() {
+        // Arrange.
+        val viewStructure: ViewStructure = FakeViewStructure()
+
+        // Act.
+        androidAutofill.populateViewStructure(viewStructure)
+
+        // Assert.
+        assertThat(viewStructure.childCount).isEqualTo(0)
+    }
+
+    @Test
+    fun populateViewStructure_oneChild() {
+        // Arrange.
+        val autofillNode = AutofillNode(
+            >
+            autofillTypes = listOf(AutofillType.Name),
+            boundingBox = Rect(0, 0, 0, 0)
+        )
+        autofillTree += autofillNode
+
+        // Act.
+        val viewStructure = FakeViewStructure()
+        androidAutofill.populateViewStructure(viewStructure)
+
+        // Assert.
+        assertThat(viewStructure).isEqualTo(FakeViewStructure().apply {
+            children.add(FakeViewStructure().apply {
+                virtualId = autofillNode.id
+                packageName = "com.google.testpackage"
+                setAutofillType(View.AUTOFILL_TYPE_TEXT)
+                setAutofillHints(arrayOf(View.AUTOFILL_HINT_NAME))
+                setDimens(0, 0, 0, 0, 0, 0)
+            })
+        })
+    }
+
+    @Test
+    fun populateViewStructure_twoChildren() {
+        // Arrange.
+        val nameAutofillNode = AutofillNode(
+            >
+            autofillTypes = listOf(AutofillType.Name),
+            boundingBox = Rect(0, 0, 0, 0)
+        )
+        autofillTree += nameAutofillNode
+
+        val emailAutofillNode = AutofillNode(
+            >
+            autofillTypes = listOf(AutofillType.EmailAddress),
+            boundingBox = Rect(0, 0, 0, 0)
+        )
+        autofillTree += emailAutofillNode
+
+        // Act.
+        val viewStructure: ViewStructure = FakeViewStructure()
+        androidAutofill.populateViewStructure(viewStructure)
+
+        // Assert.
+        assertThat(viewStructure).isEqualTo(FakeViewStructure().apply {
+            children.add(FakeViewStructure().apply {
+                virtualId = nameAutofillNode.id
+                packageName = "com.google.testpackage"
+                setAutofillType(View.AUTOFILL_TYPE_TEXT)
+                setAutofillHints(arrayOf(View.AUTOFILL_HINT_NAME))
+                setDimens(0, 0, 0, 0, 0, 0)
+            })
+            children.add(FakeViewStructure().apply {
+                virtualId = emailAutofillNode.id
+                packageName = "com.google.testpackage"
+                setAutofillType(View.AUTOFILL_TYPE_TEXT)
+                setAutofillHints(arrayOf(View.AUTOFILL_HINT_EMAIL_ADDRESS))
+                setDimens(0, 0, 0, 0, 0, 0)
+            })
+        })
+    }
+}
\ No newline at end of file