Autofill Interface and Implementation
Bug: 138548805
Test: Included new tests in this CL
Change-Id: I240e6d0b7a01719c097cea9438c0f5a88feddac3
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