[go: nahoru, domu]

blob: cfb4a5374b734594404d4ce80fb6d03964838cf1 [file] [log] [blame]
/*
* 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.compose.ui.text.input
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.text.AtomicReference
/**
* Handles communication with the IME. Informs about the IME changes via [EditCommand]s and
* provides utilities for working with software keyboard.
*
* This class is responsible for ensuring there is only one open [TextInputSession] which will
* interact with software keyboards. Start new a TextInputSession by calling [startInput] and
* close it with [stopInput].
*/
// Open for testing purposes.
open class TextInputService(private val platformTextInputService: PlatformTextInputService) {
private val _currentInputSession: AtomicReference<TextInputSession?> =
AtomicReference(null)
internal val currentInputSession: TextInputSession?
get() = _currentInputSession.get()
/**
* Start text input session for given client.
*
* If there is a previous [TextInputSession] open, it will immediately be closed by this call
* to [startInput].
*
* @param value initial [TextFieldValue]
* @param imeOptions IME configuration
* @param onEditCommand callback to inform about changes requested by IME
* @param onImeActionPerformed callback to inform if an IME action such as [ImeAction.Done]
* etc occurred.
*/
open fun startInput(
value: TextFieldValue,
imeOptions: ImeOptions,
onEditCommand: (List<EditCommand>) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
): TextInputSession {
platformTextInputService.startInput(
value,
imeOptions,
onEditCommand,
onImeActionPerformed
)
val nextSession = TextInputSession(this, platformTextInputService)
_currentInputSession.set(nextSession)
return nextSession
}
/**
* Stop text input session.
*
* If the [session] is not the currently open session, no action will occur.
*
* @param session the session returned by [startInput] call.
*/
open fun stopInput(session: TextInputSession) {
if (_currentInputSession.compareAndSet(session, null)) {
platformTextInputService.stopInput()
}
}
/**
* Request showing onscreen keyboard.
*
* This call will be ignored if there is not an open [TextInputSession], as it means there is
* nothing that will accept typed input. The most common way to open a TextInputSession is to
* set the focus to an editable text composable.
*
* There is no guarantee that the keyboard will be shown. The software keyboard or
* system service may silently ignore this request.
*/
@Deprecated(
message = "Use SoftwareKeyboardController.show or " +
"TextInputSession.showSoftwareKeyboard instead.",
replaceWith = ReplaceWith("textInputSession.showSoftwareKeyboard()")
)
// TODO(b/183448615) @InternalTextApi
fun showSoftwareKeyboard() {
if (_currentInputSession.get() != null) {
platformTextInputService.showSoftwareKeyboard()
}
}
/**
* Hide onscreen keyboard.
*/
@Deprecated(
message = "Use SoftwareKeyboardController.hide or " +
"TextInputSession.hideSoftwareKeyboard instead.",
replaceWith = ReplaceWith("textInputSession.hideSoftwareKeyboard()")
)
// TODO(b/183448615) @InternalTextApi
fun hideSoftwareKeyboard(): Unit = platformTextInputService.hideSoftwareKeyboard()
}
/**
* Represents a input session for interactions between a soft keyboard and editable text.
*
* This session may be closed at any time by [TextInputService] or by calling [dispose], after
* which [isOpen] will return false and all further calls will have no effect.
*/
class TextInputSession(
private val textInputService: TextInputService,
private val platformTextInputService: PlatformTextInputService
) {
/**
* If this session is currently open.
*
* A session may be closed at any time by [TextInputService] or by calling [dispose].
*/
val isOpen: Boolean
get() = textInputService.currentInputSession == this
/**
* Close this input session.
*
* All further calls to this object will have no effect, and [isOpen] will return false.
*
* Note, [TextInputService] may also close this input session at any time without calling
* dispose. Calling dispose after this session has been closed has no effect.
*/
fun dispose() {
textInputService.stopInput(this)
}
/**
* Execute [block] if [isOpen] is true.
*
* This function will only check [isOpen] once, and may execute the action after the input
* session closes in the case of concurrent execution.
*
* @param block action to take if isOpen
* @return true if an action was performed
*/
private inline fun ensureOpenSession(block: () -> Unit): Boolean {
return isOpen.also { applying ->
if (applying) {
block()
}
}
}
@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]
* is the state of the buffer before the changes applied by the [newValue].
*
* [oldValue] represents the changes that was requested by IME on the buffer, and [newValue]
* is the final state of the editing buffer that was requested by the application. In cases
* where [oldValue] is not equal to [newValue], it would mean the IME suggested value is
* rejected, and the IME connection will be restarted with the newValue.
*
* If the session is not open, action will be performed.
*
* @param oldValue the value that was requested by IME on the buffer
* @param newValue final state of the editing buffer that was requested by the application
* @return false if this session expired and no action was performed
*/
fun updateState(
oldValue: TextFieldValue?,
newValue: TextFieldValue
): Boolean = ensureOpenSession {
platformTextInputService.updateState(oldValue, newValue)
}
/**
* Request showing onscreen keyboard.
*
* This call will have no effect if this session is not open.
*
* This should be used instead of [TextInputService.showSoftwareKeyboard] when implementing a
* new editable text composable to show the keyboard in response to events related to that
* composable.
*
* There is no guarantee that the keyboard will be shown. The software keyboard or
* system service may silently ignore this request.
*
* @return false if this session expired and no action was performed
*/
fun showSoftwareKeyboard(): Boolean = ensureOpenSession {
platformTextInputService.showSoftwareKeyboard()
}
/**
* Hide onscreen keyboard for a specific [TextInputSession].
*
* This call will have no effect if this session is not open.
*
* This should be used instead of [TextInputService.showSoftwareKeyboard] when implementing a
* new editable text composable to hide the keyboard in response to events related to that
* composable.
*
* @return false if this session expired and no action was performed
*/
fun hideSoftwareKeyboard(): Boolean = ensureOpenSession {
platformTextInputService.hideSoftwareKeyboard()
}
}
/**
* Platform specific text input service.
*/
interface PlatformTextInputService {
/**
* Start text input session for given client.
*
* @see TextInputService.startInput
*/
fun startInput(
value: TextFieldValue,
imeOptions: ImeOptions,
onEditCommand: (List<EditCommand>) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
)
/**
* Stop text input session.
*
* @see TextInputService.stopInput
*/
fun stopInput()
/**
* Request showing onscreen keyboard
*
* There is no guarantee nor callback of the result of this API.
*
* @see TextInputService.showSoftwareKeyboard
*/
fun showSoftwareKeyboard()
/**
* Hide software keyboard
*
* @see TextInputService.hideSoftwareKeyboard
*/
fun hideSoftwareKeyboard()
/*
* Notify the new editor model to IME.
*
* @see TextInputService.updateState
*/
fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue)
@Deprecated("This method should not be called, used BringIntoViewRequester instead.")
fun notifyFocusedRect(rect: Rect) {
}
}