Don't do special handling for clearFocus on API 27 and below
Bug: 318968220
Fixes: 350534714
This reverts aosp/2928744 because it is causing problems when
focus first arrives into the ComposeView.
When the IME has a NEXT action from an external View, it
can arrive into the ComposeView's requestFocus() with
isInTouchMode = true. We can't distinguish this behavior
from the clearFocus() behavior that aosp/2928744 was
trying to fix.
Test: new test
Change-Id: I99d9d63c6007823dd7be203ce968496e6c0feb9e
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt
index 7fda49e..08b702e 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt
@@ -18,7 +18,6 @@
import android.view.View
import androidx.compose.foundation.layout.Box
-import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusStateImpl.Active
@@ -30,13 +29,8 @@
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.assertIsFocused
-import androidx.compose.ui.test.assertIsNotFocused
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.requestFocus
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
@@ -345,25 +339,6 @@
}
}
- @Test
- fun clearFocus_textFieldLosesFocus() {
- // Arrange.
- val textField = "textField"
- rule.setTestContent(extraItemForInitialFocus = false) {
- TextField(value = "", onValueChange = {}, modifier = Modifier.testTag(textField))
- }
- rule.onNodeWithTag(textField).requestFocus()
-
- // Act.
- rule.runOnIdle { focusManager.clearFocus() }
-
- // Assert.
- when (inputModeManager.inputMode) {
- Keyboard -> rule.onNodeWithTag(textField).assertIsFocused()
- Touch -> rule.onNodeWithTag(textField).assertIsNotFocused()
- }
- }
-
private val FocusManager.rootFocusState: FocusState
get() = (this as FocusOwnerImpl).rootFocusNode.focusState
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt
index d4aebb8..6569265 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.focus
+import android.os.Build
import android.view.View
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
@@ -163,8 +164,15 @@
assertThat(view.isFocused).isTrue()
}
Touch -> {
- assertThat(root.focusOwner.rootState).isEqualTo(Inactive)
- assertThat(view.isFocused).isFalse()
+ // On devices pre-P, clearFocus() will cause a subsequent requestFocus()
+ // the causes another request for focus on the ComposeView.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ assertThat(root.focusOwner.rootState).isEqualTo(ActiveParent)
+ assertThat(view.isFocused).isTrue()
+ } else {
+ assertThat(root.focusOwner.rootState).isEqualTo(Inactive)
+ assertThat(view.isFocused).isFalse()
+ }
}
else -> error("invalid input mode")
}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/FocusSearchForwardInteropTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/FocusSearchForwardInteropTest.kt
index a599981..c34238f 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/FocusSearchForwardInteropTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/FocusSearchForwardInteropTest.kt
@@ -20,11 +20,16 @@
import android.view.KeyEvent as AndroidKeyEvent
import android.view.KeyEvent.ACTION_DOWN
import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.widget.EditText
import android.widget.LinearLayout
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.layout.size
+import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
@@ -631,6 +636,46 @@
rule.onNodeWithTag(composable).assertIsNotFocused()
}
+ @Test
+ fun focusableInTouchMode() {
+ val tag = "tag"
+ lateinit var editText: EditText
+ lateinit var composeView: ComposeView
+ setContent {
+ AndroidView(
+ {
+ LinearLayout(it).also { linearLayout ->
+ linearLayout.orientation = LinearLayout.VERTICAL
+ linearLayout.addView(EditText(linearLayout.context))
+ editText = EditText(linearLayout.context)
+ editText.setSingleLine()
+ editText.setText("1")
+ editText.inputType = EditorInfo.TYPE_NUMBER_VARIATION_NORMAL
+ editText.imeOptions = EditorInfo.IME_FLAG_NAVIGATE_NEXT
+ linearLayout.addView(editText)
+ composeView =
+ ComposeView(linearLayout.context).apply {
+ setContent {
+ Column { TextField("Hello World", {}, Modifier.testTag(tag)) }
+ }
+ }
+ linearLayout.addView(composeView)
+ }
+ },
+ Modifier.safeContentPadding()
+ )
+ }
+ rule.runOnIdle {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ instrumentation.setInTouchMode(true)
+ editText.requestFocusFromTouch()
+ }
+ rule.waitUntil { rule.runOnUiThread { editText.isFocused } }
+ rule.waitForIdle()
+ rule.runOnIdle { editText.onEditorAction(EditorInfo.IME_ACTION_NEXT) }
+ rule.onNodeWithTag(tag).assertIsFocused()
+ }
+
private fun ComposeContentTestRule.focusSearchForward(waitForIdle: Boolean = true) {
if (waitForIdle) waitForIdle()
if (moveFocusProgrammatically) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 9961afd..5a2b22d 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -916,11 +916,6 @@
return super.requestFocus(direction, previouslyFocusedRect)
}
- // When we clear focus on Pre P devices, request focus is called even when we are
- // in touch mode. We fix this by assigning initial focus only in non-touch mode.
- // https://developer.android.com/about/versions/pie/android-9.0-changes-28#focus
- if (isInTouchMode) return false
-
val focusDirection = toFocusDirection(direction) ?: Enter
return focusOwner.focusSearch(
focusDirection = focusDirection,