Revert removing implementations of notifyFocusedRect.

The notifyFocusedRect methods on TextInputService and TextInputSession
were deprecated and had their implementations replaced by no-ops in the
change aosp/1959647. This was probably too aggressive, so this change
adds the old implementations back in but leaves them deprecated, in case
any third-party code was calling them. The implementations were actually
broken before, and this change doesn't try to fix them, it just restores
the previous broken behavior.

Discussion thread about revert:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1959647/comments/81f97bed_7f38e815

Bug: b/192043120
Bug: b/178211874
Test: Old tests restored.
Change-Id: I385749b629fd35b72bd148122ab1ec0f6d4ffa96
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
index a97d3cd..79cbf37 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/TextInputService.kt
@@ -147,9 +147,11 @@
         }
     }
 
-    @Suppress("DeprecatedCallableAddReplaceWith", "UNUSED_PARAMETER")
-    @Deprecated("This method is not called, used BringIntoViewRequester instead.")
-    fun notifyFocusedRect(rect: Rect): Boolean = false
+    @Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION")
+    @Deprecated("This method should not be called, used BringIntoViewRequester instead.")
+    fun notifyFocusedRect(rect: Rect): Boolean = ensureOpenSession {
+        platformTextInputService.notifyFocusedRect(rect)
+    }
 
     /**
      * Notify IME about the new [TextFieldValue] and latest state of the editing buffer. [oldValue]
@@ -253,7 +255,7 @@
      */
     fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue)
 
-    @Deprecated("This method is not called, used BringIntoViewRequester instead.")
+    @Deprecated("This method should not be called, used BringIntoViewRequester instead.")
     fun notifyFocusedRect(rect: Rect) {
     }
 }
diff --git a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextInputServiceTest.kt b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextInputServiceTest.kt
index c904ccd..10b25c6 100644
--- a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextInputServiceTest.kt
+++ b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/TextInputServiceTest.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.ui.text
 
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.text.input.ImeOptions
 import androidx.compose.ui.text.input.PlatformTextInputService
 import androidx.compose.ui.text.input.TextFieldValue
@@ -31,6 +34,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+@Suppress("DEPRECATION")
 @RunWith(JUnit4::class)
 class TextInputServiceTest {
 
@@ -235,4 +239,51 @@
         secondSession.updateState(null, editorModel)
         verify(platformService).updateState(eq(null), eq(editorModel))
     }
+
+    @Test
+    fun notifyFocusedRect_with_valid_token() {
+        val platformService = mock<PlatformTextInputService>()
+
+        val textInputService = TextInputService(platformService)
+
+        val firstSession = textInputService.startInput(
+            TextFieldValue(),
+            ImeOptions.Default,
+            {}, // onEditCommand
+            {} // onImeActionPerformed
+        )
+
+        val rect = Rect(Offset.Zero, Size(100f, 100f))
+        firstSession.notifyFocusedRect(rect)
+        verify(platformService, times(1)).notifyFocusedRect(eq(rect))
+    }
+
+    @Test
+    fun notifyFocusedRect_with_expired_token() {
+        val platformService = mock<PlatformTextInputService>()
+
+        val textInputService = TextInputService(platformService)
+
+        val firstSession = textInputService.startInput(
+            TextFieldValue(),
+            ImeOptions.Default,
+            {}, // onEditCommand
+            {} // onImeActionPerformed
+        )
+
+        // Start another session. The firstToken is now expired.
+        val secondSession = textInputService.startInput(
+            TextFieldValue(),
+            ImeOptions.Default,
+            {}, // onEditCommand
+            {} // onImeActionPerformed
+        )
+
+        val rect = Rect(Offset.Zero, Size(100f, 100f))
+        firstSession.notifyFocusedRect(rect)
+        verify(platformService, never()).notifyFocusedRect(any())
+
+        secondSession.notifyFocusedRect(rect)
+        verify(platformService, times(1)).notifyFocusedRect(eq(rect))
+    }
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
index 39afec1..d46d49d 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.text.input
 
+import android.graphics.Rect as AndroidRect
 import android.text.InputType
 import android.util.Log
 import android.view.KeyEvent
@@ -23,12 +24,14 @@
 import android.view.inputmethod.BaseInputConnection
 import android.view.inputmethod.EditorInfo
 import android.view.inputmethod.InputConnection
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.TextInputServiceAndroid.TextInputCommand.HideKeyboard
 import androidx.compose.ui.text.input.TextInputServiceAndroid.TextInputCommand.ShowKeyboard
 import androidx.compose.ui.text.input.TextInputServiceAndroid.TextInputCommand.StartInput
 import androidx.compose.ui.text.input.TextInputServiceAndroid.TextInputCommand.StopInput
 import androidx.core.view.inputmethod.EditorInfoCompat
+import kotlin.math.roundToInt
 import kotlinx.coroutines.channels.Channel
 
 private const val DEBUG_CLASS = "TextInputServiceAndroid"
@@ -70,11 +73,14 @@
         private set
     private var imeOptions = ImeOptions.Default
     private var ic: RecordingInputConnection? = null
+
     // used for sendKeyEvent delegation
     private val baseInputConnection by lazy(LazyThreadSafetyMode.NONE) {
         BaseInputConnection(view, false)
     }
 
+    private var focusedRect: AndroidRect? = null
+
     /**
      * A channel that is used to debounce rapid operations such as showing/hiding the keyboard and
      * starting/stopping input, so we can make the minimal number of calls on the
@@ -153,6 +159,7 @@
         editorHasFocus = false
         onEditCommand = {}
         onImeActionPerformed = {}
+        focusedRect = null
 
         // Don't actually send the command to the IME yet, it may be overruled by a subsequent call
         // to startInput.
@@ -327,6 +334,30 @@
         }
     }
 
+    @Suppress("OverridingDeprecatedMember")
+    override fun notifyFocusedRect(rect: Rect) {
+        focusedRect = AndroidRect(
+            rect.left.roundToInt(),
+            rect.top.roundToInt(),
+            rect.right.roundToInt(),
+            rect.bottom.roundToInt()
+        )
+
+        // Requesting rectangle too early after obtaining focus may bring view into wrong place
+        // probably due to transient IME inset change. We don't know the correct timing of calling
+        // requestRectangleOnScreen API, so try to call this API only after the IME is ready to
+        // use, i.e. InputConnection has created.
+        // Even if we miss all the timing of requesting rectangle during initial text field focus,
+        // focused rectangle will be requested when software keyboard has shown.
+        if (ic == null) {
+            focusedRect?.let {
+                // Notice that view.requestRectangleOnScreen may modify the input Rect, we have to
+                // create another Rect and then pass it.
+                view.requestRectangleOnScreen(AndroidRect(it))
+            }
+        }
+    }
+
     /** Immediately restart the IME connection, bypassing the [textInputCommandChannel]. */
     private fun restartInputImmediately() {
         if (DEBUG) Log.d(TAG, "$DEBUG_CLASS.restartInputImmediately")