[go: nahoru, domu]

blob: 172194a3cdc011101996f8fd5049df8ff2032249 [file] [log] [blame]
Seigo Nonaka4b211592019-04-07 14:11:28 -07001/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Louis Pullen-Freilichab194752020-07-21 22:21:22 +010017package androidx.compose.ui.text.input
Seigo Nonaka4b211592019-04-07 14:11:28 -070018
19import android.content.Context
Seigo Nonaka4b211592019-04-07 14:11:28 -070020import android.text.InputType
21import android.view.View
Seigo Nonaka9a92fd92020-06-19 15:58:02 -070022import android.view.ViewTreeObserver
Seigo Nonaka4b211592019-04-07 14:11:28 -070023import android.view.inputmethod.EditorInfo
24import android.view.inputmethod.InputConnection
25import android.view.inputmethod.InputMethodManager
George Mount842c8c12020-01-08 16:03:42 -080026import androidx.ui.geometry.Rect
Louis Pullen-Freilichab194752020-07-21 22:21:22 +010027import androidx.compose.ui.text.TextRange
Seigo Nonakadd60a5c2019-07-10 11:23:36 -070028import kotlin.math.roundToInt
Seigo Nonaka4b211592019-04-07 14:11:28 -070029
30/**
31 * Provide Android specific input service with the Operating System.
32 */
Siyamed Sinir9da613b2019-11-15 20:39:19 -080033internal class TextInputServiceAndroid(val view: View) : PlatformTextInputService {
Louis Pullen-Freilich5da28bd2019-10-15 17:05:07 +010034 /** True if the currently editable composable has connected */
Seigo Nonaka4b211592019-04-07 14:11:28 -070035 private var editorHasFocus = false
36
37 /**
Louis Pullen-Freilich5da28bd2019-10-15 17:05:07 +010038 * The following three observers are set when the editable composable has initiated the input
Seigo Nonaka4b211592019-04-07 14:11:28 -070039 * session
40 */
Seigo Nonaka615f32d2019-06-18 15:33:38 -070041 private var onEditCommand: (List<EditOperation>) -> Unit = {}
Seigo Nonakac04f629d2019-07-11 13:20:30 -070042 private var onImeActionPerformed: (ImeAction) -> Unit = {}
Seigo Nonaka4b211592019-04-07 14:11:28 -070043
Siyamed Sinir01b35f72020-06-15 19:15:32 -070044 private var state = TextFieldValue(text = "", selection = TextRange.Zero)
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -070045 private var keyboardType = KeyboardType.Text
Seigo Nonakac04f629d2019-07-11 13:20:30 -070046 private var imeAction = ImeAction.Unspecified
Seigo Nonaka4e45c462019-07-01 12:12:59 -070047 private var ic: RecordingInputConnection? = null
48
Seigo Nonaka9a92fd92020-06-19 15:58:02 -070049 private var focusedRect: android.graphics.Rect? = null
50
Seigo Nonaka4b211592019-04-07 14:11:28 -070051 /**
52 * The editable buffer used for BaseInputConnection.
53 */
Ralston Da Silvadff6b622019-08-07 15:29:06 -070054 private lateinit var imm: InputMethodManager
Seigo Nonaka4b211592019-04-07 14:11:28 -070055
Seigo Nonaka9a92fd92020-06-19 15:58:02 -070056 private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
57 // focusedRect is null if there is not ongoing text input session. So safe to request
58 // latest focused rectangle whenever global layout has changed.
59 focusedRect?.let { view.requestRectangleOnScreen(it) }
60 }
61
62 init {
63 view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
64 override fun onViewDetachedFromWindow(v: View?) {
65 v?.rootView?.viewTreeObserver?.removeOnGlobalLayoutListener(layoutListener)
66 }
67
68 override fun onViewAttachedToWindow(v: View?) {
69 v?.rootView?.viewTreeObserver?.addOnGlobalLayoutListener(layoutListener)
70 }
71 })
72 }
73
Seigo Nonaka4b211592019-04-07 14:11:28 -070074 /**
75 * Creates new input connection.
76 */
77 fun createInputConnection(outAttrs: EditorInfo): InputConnection? {
78 if (!editorHasFocus) {
79 return null
80 }
Seigo Nonakac04f629d2019-07-11 13:20:30 -070081 fillEditorInfo(keyboardType, imeAction, outAttrs)
Seigo Nonaka4b211592019-04-07 14:11:28 -070082
Seigo Nonaka4e45c462019-07-01 12:12:59 -070083 return RecordingInputConnection(
84 initState = state,
85 eventListener = object : InputEventListener {
86 override fun onEditOperations(editOps: List<EditOperation>) {
87 onEditCommand(editOps)
88 }
Seigo Nonakac04f629d2019-07-11 13:20:30 -070089
90 override fun onImeAction(imeAction: ImeAction) {
91 onImeActionPerformed(imeAction)
92 }
Seigo Nonaka615f32d2019-06-18 15:33:38 -070093 }
Seigo Nonaka4e45c462019-07-01 12:12:59 -070094 ).also { ic = it }
Seigo Nonaka4b211592019-04-07 14:11:28 -070095 }
96
97 /**
98 * Returns true if some editable component is focused.
99 */
100 fun isEditorFocused(): Boolean = editorHasFocus
101
102 override fun startInput(
Siyamed Sinirc2e55592020-06-09 09:37:23 -0700103 value: TextFieldValue,
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700104 keyboardType: KeyboardType,
Seigo Nonakac04f629d2019-07-11 13:20:30 -0700105 imeAction: ImeAction,
Seigo Nonaka615f32d2019-06-18 15:33:38 -0700106 onEditCommand: (List<EditOperation>) -> Unit,
Seigo Nonakac04f629d2019-07-11 13:20:30 -0700107 onImeActionPerformed: (ImeAction) -> Unit
Seigo Nonaka4b211592019-04-07 14:11:28 -0700108 ) {
Ralston Da Silvadff6b622019-08-07 15:29:06 -0700109 imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
Seigo Nonaka4b211592019-04-07 14:11:28 -0700110 editorHasFocus = true
Siyamed Sinirc2e55592020-06-09 09:37:23 -0700111 state = value
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700112 this.keyboardType = keyboardType
Seigo Nonakac04f629d2019-07-11 13:20:30 -0700113 this.imeAction = imeAction
Seigo Nonaka615f32d2019-06-18 15:33:38 -0700114 this.onEditCommand = onEditCommand
Seigo Nonakac04f629d2019-07-11 13:20:30 -0700115 this.onImeActionPerformed = onImeActionPerformed
Seigo Nonaka4b211592019-04-07 14:11:28 -0700116
117 view.requestFocus()
118 view.post {
119 imm.restartInput(view)
120 imm.showSoftInput(view, 0)
121 }
122 }
123
124 override fun stopInput() {
125 editorHasFocus = false
Seigo Nonaka615f32d2019-06-18 15:33:38 -0700126 onEditCommand = {}
Seigo Nonakac04f629d2019-07-11 13:20:30 -0700127 onImeActionPerformed = {}
Seigo Nonaka9a92fd92020-06-19 15:58:02 -0700128 focusedRect = null
Seigo Nonaka4b211592019-04-07 14:11:28 -0700129
130 imm.restartInput(view)
Seigo Nonaka6425fde2020-02-27 16:20:33 -0800131 editorHasFocus = false
Seigo Nonaka4b211592019-04-07 14:11:28 -0700132 }
Seigo Nonaka97c9f0b2019-07-01 14:22:07 -0700133
134 override fun showSoftwareKeyboard() {
135 imm.showSoftInput(view, 0)
136 }
Seigo Nonaka4e45c462019-07-01 12:12:59 -0700137
Seigo Nonaka6425fde2020-02-27 16:20:33 -0800138 override fun hideSoftwareKeyboard() {
139 imm.hideSoftInputFromWindow(view.windowToken, 0)
140 }
141
Siyamed Sinirc2e55592020-06-09 09:37:23 -0700142 override fun onStateUpdated(value: TextFieldValue) {
143 this.state = value
Seigo Nonaka4e45c462019-07-01 12:12:59 -0700144 ic?.updateInputState(this.state, imm, view)
145 }
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700146
Seigo Nonakadd60a5c2019-07-10 11:23:36 -0700147 override fun notifyFocusedRect(rect: Rect) {
Seigo Nonaka9a92fd92020-06-19 15:58:02 -0700148 focusedRect = android.graphics.Rect(
Seigo Nonakadd60a5c2019-07-10 11:23:36 -0700149 rect.left.roundToInt(),
150 rect.top.roundToInt(),
151 rect.right.roundToInt(),
Seigo Nonaka9a92fd92020-06-19 15:58:02 -0700152 rect.bottom.roundToInt()
153 )
154
155 // Requesting rectangle too early after obtaining focus may bring view into wrong place
156 // probably due to transient IME inset change. We don't know the correct timing of calling
157 // requestRectangleOnScreen API, so try to call this API only after the IME is ready to
158 // use, i.e. InputConnection has created.
159 // Even if we miss all the timing of requesting rectangle during initial text field focus,
160 // focused rectangle will be requested when software keyboard has shown.
161 if (ic == null) {
162 view.requestRectangleOnScreen(focusedRect)
163 }
Seigo Nonakadd60a5c2019-07-10 11:23:36 -0700164 }
165
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700166 /**
167 * Fills necessary info of EditorInfo.
168 */
Seigo Nonakac04f629d2019-07-11 13:20:30 -0700169 private fun fillEditorInfo(
170 keyboardType: KeyboardType,
171 imeAction: ImeAction,
172 outInfo: EditorInfo
173 ) {
174 outInfo.imeOptions = when (imeAction) {
175 ImeAction.Unspecified -> EditorInfo.IME_ACTION_UNSPECIFIED
176 ImeAction.NoAction -> EditorInfo.IME_ACTION_NONE
177 ImeAction.Go -> EditorInfo.IME_ACTION_GO
178 ImeAction.Next -> EditorInfo.IME_ACTION_NEXT
179 ImeAction.Previous -> EditorInfo.IME_ACTION_PREVIOUS
180 ImeAction.Search -> EditorInfo.IME_ACTION_SEARCH
181 ImeAction.Send -> EditorInfo.IME_ACTION_SEND
182 ImeAction.Done -> EditorInfo.IME_ACTION_DONE
183 else -> throw IllegalArgumentException("Unknown ImeAction: $imeAction")
184 }
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700185 when (keyboardType) {
186 KeyboardType.Text -> outInfo.inputType = InputType.TYPE_CLASS_TEXT
Siyamed Sinir3a952ea2019-07-08 16:50:50 -0700187 KeyboardType.Ascii -> {
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700188 outInfo.inputType = InputType.TYPE_CLASS_TEXT
Seigo Nonakac04f629d2019-07-11 13:20:30 -0700189 outInfo.imeOptions = outInfo.imeOptions or EditorInfo.IME_FLAG_FORCE_ASCII
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700190 }
191 KeyboardType.Number -> outInfo.inputType = InputType.TYPE_CLASS_NUMBER
192 KeyboardType.Phone -> outInfo.inputType = InputType.TYPE_CLASS_PHONE
Siyamed Sinir3a952ea2019-07-08 16:50:50 -0700193 KeyboardType.Uri ->
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700194 outInfo.inputType = InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI
195 KeyboardType.Email ->
196 outInfo.inputType =
197 InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
Seigo Nonaka813dc152019-07-15 17:15:05 -0700198 KeyboardType.Password -> {
199 outInfo.inputType =
200 InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
201 }
202 KeyboardType.NumberPassword -> {
203 outInfo.inputType =
204 InputType.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD
205 }
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700206 else -> throw IllegalArgumentException("Unknown KeyboardType: $keyboardType")
207 }
Seigo Nonakac04f629d2019-07-11 13:20:30 -0700208 outInfo.imeOptions =
209 outInfo.imeOptions or outInfo.imeOptions or EditorInfo.IME_FLAG_NO_FULLSCREEN
Seigo Nonaka6e6e2ee2019-07-01 17:18:41 -0700210 }
Seigo Nonakaa7e05d72019-09-30 17:31:25 -0700211}