[go: nahoru, domu]

Autofill Interface and Implementation

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