more readable kotlin files, get rid of much !!

This commit is contained in:
Helium314 2023-09-16 20:12:47 +02:00
parent 9c091e7f31
commit 6e46f5c9f6
6 changed files with 199 additions and 221 deletions

View file

@ -18,9 +18,9 @@ import org.dslul.openboard.inputmethod.latin.SuggestedWords
import org.dslul.openboard.inputmethod.latin.utils.InputTypeUtils import org.dslul.openboard.inputmethod.latin.utils.InputTypeUtils
class AccessibilityUtils private constructor() { class AccessibilityUtils private constructor() {
private var mContext: Context? = null private lateinit var mContext: Context
private var mAccessibilityManager: AccessibilityManager? = null private lateinit var mAccessibilityManager: AccessibilityManager
private var mAudioManager: AudioManager? = null private lateinit var mAudioManager: AudioManager
/** The most recent auto-correction. */ /** The most recent auto-correction. */
private var mAutoCorrectionWord: String? = null private var mAutoCorrectionWord: String? = null
/** The most recent typed word for auto-correction. */ /** The most recent typed word for auto-correction. */
@ -39,7 +39,7 @@ class AccessibilityUtils private constructor() {
* @return `true` if accessibility is enabled. * @return `true` if accessibility is enabled.
*/ */
val isAccessibilityEnabled: Boolean val isAccessibilityEnabled: Boolean
get() = ENABLE_ACCESSIBILITY && mAccessibilityManager!!.isEnabled get() = ENABLE_ACCESSIBILITY && mAccessibilityManager.isEnabled
/** /**
* Returns `true` if touch exploration is enabled. Currently, this * Returns `true` if touch exploration is enabled. Currently, this
@ -49,7 +49,7 @@ class AccessibilityUtils private constructor() {
* @return `true` if touch exploration is enabled. * @return `true` if touch exploration is enabled.
*/ */
val isTouchExplorationEnabled: Boolean val isTouchExplorationEnabled: Boolean
get() = isAccessibilityEnabled && mAccessibilityManager!!.isTouchExplorationEnabled get() = isAccessibilityEnabled && mAccessibilityManager.isTouchExplorationEnabled
/** /**
* Returns whether the device should obscure typed password characters. * Returns whether the device should obscure typed password characters.
@ -61,12 +61,12 @@ class AccessibilityUtils private constructor() {
if (editorInfo == null) return false if (editorInfo == null) return false
// The user can optionally force speaking passwords. // The user can optionally force speaking passwords.
if (Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD != null) { if (Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD != null) {
val speakPassword = Settings.Secure.getInt(mContext!!.contentResolver, val speakPassword = Settings.Secure.getInt(mContext.contentResolver,
Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0
if (speakPassword) return false if (speakPassword) return false
} }
// Always speak if the user is listening through headphones. // Always speak if the user is listening through headphones.
return if (mAudioManager!!.isWiredHeadsetOn || mAudioManager!!.isBluetoothA2dpOn) { return if (mAudioManager.isWiredHeadsetOn || mAudioManager.isBluetoothA2dpOn) {
false false
} else InputTypeUtils.isPasswordInputType(editorInfo.inputType) } else InputTypeUtils.isPasswordInputType(editorInfo.inputType)
// Don't speak if the IME is connected to a password field. // Don't speak if the IME is connected to a password field.
@ -105,9 +105,9 @@ class AccessibilityUtils private constructor() {
if (!TextUtils.isEmpty(mAutoCorrectionWord)) { if (!TextUtils.isEmpty(mAutoCorrectionWord)) {
if (!TextUtils.equals(mAutoCorrectionWord, mTypedWord)) { if (!TextUtils.equals(mAutoCorrectionWord, mTypedWord)) {
return if (shouldObscure) { // This should never happen, but just in case... return if (shouldObscure) { // This should never happen, but just in case...
mContext!!.getString(R.string.spoken_auto_correct_obscured, mContext.getString(R.string.spoken_auto_correct_obscured,
keyCodeDescription) keyCodeDescription)
} else mContext!!.getString(R.string.spoken_auto_correct, keyCodeDescription, } else mContext.getString(R.string.spoken_auto_correct, keyCodeDescription,
mTypedWord, mAutoCorrectionWord) mTypedWord, mAutoCorrectionWord)
} }
} }
@ -122,7 +122,7 @@ class AccessibilityUtils private constructor() {
* @param text The text to speak. * @param text The text to speak.
*/ */
fun announceForAccessibility(view: View, text: CharSequence?) { fun announceForAccessibility(view: View, text: CharSequence?) {
if (!mAccessibilityManager!!.isEnabled) { if (!mAccessibilityManager.isEnabled) {
Log.e(TAG, "Attempted to speak when accessibility was disabled!") Log.e(TAG, "Attempted to speak when accessibility was disabled!")
return return
} }
@ -154,10 +154,9 @@ class AccessibilityUtils private constructor() {
* @param editorInfo The input connection's editor info attribute. * @param editorInfo The input connection's editor info attribute.
* @param restarting Whether the connection is being restarted. * @param restarting Whether the connection is being restarted.
*/ */
fun onStartInputViewInternal(view: View, editorInfo: EditorInfo?, fun onStartInputViewInternal(view: View, editorInfo: EditorInfo?, restarting: Boolean) {
restarting: Boolean) {
if (shouldObscureInput(editorInfo)) { if (shouldObscureInput(editorInfo)) {
val text = mContext!!.getText(R.string.spoken_use_headphones) val text = mContext.getText(R.string.spoken_use_headphones)
announceForAccessibility(view, text) announceForAccessibility(view, text)
} }
} }
@ -169,8 +168,8 @@ class AccessibilityUtils private constructor() {
* @param event The event to send. * @param event The event to send.
*/ */
fun requestSendAccessibilityEvent(event: AccessibilityEvent?) { fun requestSendAccessibilityEvent(event: AccessibilityEvent?) {
if (mAccessibilityManager!!.isEnabled) { if (mAccessibilityManager.isEnabled) {
mAccessibilityManager!!.sendAccessibilityEvent(event) mAccessibilityManager.sendAccessibilityEvent(event)
} }
} }

View file

@ -15,7 +15,30 @@ import java.util.*
internal class KeyCodeDescriptionMapper private constructor() { internal class KeyCodeDescriptionMapper private constructor() {
// Sparse array of spoken description resource IDs indexed by key codes // Sparse array of spoken description resource IDs indexed by key codes
private val mKeyCodeMap = SparseIntArray() private val mKeyCodeMap = SparseIntArray().apply {
// Special non-character codes defined in Keyboard
put(Constants.CODE_SPACE, R.string.spoken_description_space)
put(Constants.CODE_DELETE, R.string.spoken_description_delete)
put(Constants.CODE_ENTER, R.string.spoken_description_return)
put(Constants.CODE_SETTINGS, R.string.spoken_description_settings)
put(Constants.CODE_SHIFT, R.string.spoken_description_shift)
put(Constants.CODE_SHORTCUT, R.string.spoken_description_mic)
put(Constants.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol)
put(Constants.CODE_TAB, R.string.spoken_description_tab)
put(Constants.CODE_LANGUAGE_SWITCH, R.string.spoken_description_language_switch)
put(Constants.CODE_ACTION_NEXT, R.string.spoken_description_action_next)
put(Constants.CODE_ACTION_PREVIOUS, R.string.spoken_description_action_previous)
put(Constants.CODE_EMOJI, R.string.spoken_description_emoji)
// Because the upper-case and lower-case mappings of the following letters is depending on
// the locale, the upper case descriptions should be defined here. The lower case
// descriptions are handled in {@link #getSpokenLetterDescriptionId(Context,int)}.
// U+0049: "I" LATIN CAPITAL LETTER I
// U+0069: "i" LATIN SMALL LETTER I
// U+0130: "İ" LATIN CAPITAL LETTER I WITH DOT ABOVE
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
put(0x0049, R.string.spoken_letter_0049)
put(0x0130, R.string.spoken_letter_0130)
}
/** /**
* Returns the localized description of the action performed by a specified * Returns the localized description of the action performed by a specified
@ -27,8 +50,7 @@ internal class KeyCodeDescriptionMapper private constructor() {
* @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured.
* @return a character sequence describing the action performed by pressing the key * @return a character sequence describing the action performed by pressing the key
*/ */
fun getDescriptionForKey(context: Context, keyboard: Keyboard?, fun getDescriptionForKey(context: Context, keyboard: Keyboard?, key: Key, shouldObscure: Boolean): String? {
key: Key, shouldObscure: Boolean): String? {
val code = key.code val code = key.code
if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
val description = getDescriptionForSwitchAlphaSymbol(context, keyboard) val description = getDescriptionForSwitchAlphaSymbol(context, keyboard)
@ -39,17 +61,19 @@ internal class KeyCodeDescriptionMapper private constructor() {
if (code == Constants.CODE_SHIFT) { if (code == Constants.CODE_SHIFT) {
return getDescriptionForShiftKey(context, keyboard) return getDescriptionForShiftKey(context, keyboard)
} }
if (code == Constants.CODE_ENTER) { // The following function returns the correct description in all action and if (code == Constants.CODE_ENTER) {
// The following function returns the correct description in all action and
// regular enter cases, taking care of all modes. // regular enter cases, taking care of all modes.
return getDescriptionForActionKey(context, keyboard, key) return getDescriptionForActionKey(context, keyboard, key)
} }
if (code == Constants.CODE_OUTPUT_TEXT) { if (code == Constants.CODE_OUTPUT_TEXT) {
val outputText = key.outputText val outputText = key.outputText ?: return context.getString(R.string.spoken_description_unknown)
val description = getSpokenEmoticonDescription(context, outputText) val description = getSpokenEmoticonDescription(context, outputText)
return if (TextUtils.isEmpty(description)) outputText else description return if (TextUtils.isEmpty(description)) outputText else description
} }
// Just attempt to speak the description. // Just attempt to speak the description.
if (code != Constants.CODE_UNSPECIFIED) { // If the key description should be obscured, now is the time to do it. if (code != Constants.CODE_UNSPECIFIED) {
// If the key description should be obscured, now is the time to do it.
val isDefinedNonCtrl = (Character.isDefined(code) val isDefinedNonCtrl = (Character.isDefined(code)
&& !Character.isISOControl(code)) && !Character.isISOControl(code))
if (shouldObscure && isDefinedNonCtrl) { if (shouldObscure && isDefinedNonCtrl) {
@ -74,7 +98,8 @@ internal class KeyCodeDescriptionMapper private constructor() {
* @param codePoint The code point from which to obtain a description. * @param codePoint The code point from which to obtain a description.
* @return a character sequence describing the code point. * @return a character sequence describing the code point.
*/ */
fun getDescriptionForCodePoint(context: Context, codePoint: Int): String? { // If the key description should be obscured, now is the time to do it. fun getDescriptionForCodePoint(context: Context, codePoint: Int): String? {
// If the key description should be obscured, now is the time to do it.
val index = mKeyCodeMap.indexOfKey(codePoint) val index = mKeyCodeMap.indexOfKey(codePoint)
if (index >= 0) { if (index >= 0) {
return context.getString(mKeyCodeMap.valueAt(index)) return context.getString(mKeyCodeMap.valueAt(index))
@ -141,8 +166,7 @@ internal class KeyCodeDescriptionMapper private constructor() {
val resourceName = String.format(Locale.ROOT, resourceNameFormat, code) val resourceName = String.format(Locale.ROOT, resourceNameFormat, code)
val resources = context.resources val resources = context.resources
// Note that the resource package name may differ from the context package name. // Note that the resource package name may differ from the context package name.
val resourcePackageName = resources.getResourcePackageName( val resourcePackageName = resources.getResourcePackageName(R.string.spoken_description_unknown)
R.string.spoken_description_unknown)
val resId = resources.getIdentifier(resourceName, "string", resourcePackageName) val resId = resources.getIdentifier(resourceName, "string", resourcePackageName)
if (resId != 0) { if (resId != 0) {
mKeyCodeMap.append(code, resId) mKeyCodeMap.append(code, resId)
@ -170,12 +194,8 @@ internal class KeyCodeDescriptionMapper private constructor() {
* @param keyboard The keyboard on which the key resides. * @param keyboard The keyboard on which the key resides.
* @return a character sequence describing the action performed by pressing the key * @return a character sequence describing the action performed by pressing the key
*/ */
private fun getDescriptionForSwitchAlphaSymbol(context: Context, private fun getDescriptionForSwitchAlphaSymbol(context: Context, keyboard: Keyboard?): String? {
keyboard: Keyboard?): String? { val resId = when (val elementId = keyboard?.mId?.mElementId) {
val keyboardId = keyboard!!.mId
val elementId = keyboardId.mElementId
val resId: Int
resId = when (elementId) {
KeyboardId.ELEMENT_ALPHABET, KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED, KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED -> R.string.spoken_description_to_symbol KeyboardId.ELEMENT_ALPHABET, KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED, KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED -> R.string.spoken_description_to_symbol
KeyboardId.ELEMENT_SYMBOLS, KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> R.string.spoken_description_to_alpha KeyboardId.ELEMENT_SYMBOLS, KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> R.string.spoken_description_to_alpha
KeyboardId.ELEMENT_PHONE -> R.string.spoken_description_to_symbol KeyboardId.ELEMENT_PHONE -> R.string.spoken_description_to_symbol
@ -195,12 +215,8 @@ internal class KeyCodeDescriptionMapper private constructor() {
* @param keyboard The keyboard on which the key resides. * @param keyboard The keyboard on which the key resides.
* @return A context-sensitive description of the "Shift" key. * @return A context-sensitive description of the "Shift" key.
*/ */
private fun getDescriptionForShiftKey(context: Context, private fun getDescriptionForShiftKey(context: Context, keyboard: Keyboard?): String {
keyboard: Keyboard?): String { val resId: Int = when (keyboard?.mId?.mElementId) {
val keyboardId = keyboard!!.mId
val elementId = keyboardId.mElementId
val resId: Int
resId = when (elementId) {
KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED, KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED -> R.string.spoken_description_caps_lock KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED, KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED -> R.string.spoken_description_caps_lock
KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED -> R.string.spoken_description_shift_shifted KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED -> R.string.spoken_description_shift_shifted
KeyboardId.ELEMENT_SYMBOLS -> R.string.spoken_description_symbols_shift KeyboardId.ELEMENT_SYMBOLS -> R.string.spoken_description_symbols_shift
@ -218,16 +234,12 @@ internal class KeyCodeDescriptionMapper private constructor() {
* @param key The key to describe. * @param key The key to describe.
* @return Returns a context-sensitive description of the "Enter" action key. * @return Returns a context-sensitive description of the "Enter" action key.
*/ */
private fun getDescriptionForActionKey(context: Context, keyboard: Keyboard?, private fun getDescriptionForActionKey(context: Context, keyboard: Keyboard?, key: Key): String {
key: Key): String {
val keyboardId = keyboard!!.mId
val actionId = keyboardId.imeAction()
val resId: Int
// Always use the label, if available. // Always use the label, if available.
if (!TextUtils.isEmpty(key.label)) { if (!TextUtils.isEmpty(key.label)) {
return key.label!!.trim { it <= ' ' } return key.label!!.trim { it <= ' ' }
} }
resId = when (actionId) { val resId = when (keyboard?.mId?.imeAction()) {
EditorInfo.IME_ACTION_SEARCH -> R.string.label_search_key EditorInfo.IME_ACTION_SEARCH -> R.string.label_search_key
EditorInfo.IME_ACTION_GO -> R.string.label_go_key EditorInfo.IME_ACTION_GO -> R.string.label_go_key
EditorInfo.IME_ACTION_SEND -> R.string.label_send_key EditorInfo.IME_ACTION_SEND -> R.string.label_send_key
@ -240,10 +252,9 @@ internal class KeyCodeDescriptionMapper private constructor() {
} }
// TODO: Remove this method once TTS supports emoticon verbalization. // TODO: Remove this method once TTS supports emoticon verbalization.
private fun getSpokenEmoticonDescription(context: Context, private fun getSpokenEmoticonDescription(context: Context, outputText: String): String? {
outputText: String?): String? {
val sb = StringBuilder(SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX) val sb = StringBuilder(SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX)
val textLength = outputText!!.length val textLength = outputText.length
var index = 0 var index = 0
while (index < textLength) { while (index < textLength) {
val codePoint = outputText.codePointAt(index) val codePoint = outputText.codePointAt(index)
@ -253,36 +264,10 @@ internal class KeyCodeDescriptionMapper private constructor() {
val resourceName = sb.toString() val resourceName = sb.toString()
val resources = context.resources val resources = context.resources
// Note that the resource package name may differ from the context package name. // Note that the resource package name may differ from the context package name.
val resourcePackageName = resources.getResourcePackageName( val resourcePackageName = resources.getResourcePackageName(R.string.spoken_description_unknown)
R.string.spoken_description_unknown)
val resId = resources.getIdentifier(resourceName, "string", resourcePackageName) val resId = resources.getIdentifier(resourceName, "string", resourcePackageName)
return if (resId == 0) null else resources.getString(resId) return if (resId == 0) null else resources.getString(resId)
} }
} }
init { // Special non-character codes defined in Keyboard
mKeyCodeMap.put(Constants.CODE_SPACE, R.string.spoken_description_space)
mKeyCodeMap.put(Constants.CODE_DELETE, R.string.spoken_description_delete)
mKeyCodeMap.put(Constants.CODE_ENTER, R.string.spoken_description_return)
mKeyCodeMap.put(Constants.CODE_SETTINGS, R.string.spoken_description_settings)
mKeyCodeMap.put(Constants.CODE_SHIFT, R.string.spoken_description_shift)
mKeyCodeMap.put(Constants.CODE_SHORTCUT, R.string.spoken_description_mic)
mKeyCodeMap.put(Constants.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol)
mKeyCodeMap.put(Constants.CODE_TAB, R.string.spoken_description_tab)
mKeyCodeMap.put(Constants.CODE_LANGUAGE_SWITCH,
R.string.spoken_description_language_switch)
mKeyCodeMap.put(Constants.CODE_ACTION_NEXT, R.string.spoken_description_action_next)
mKeyCodeMap.put(Constants.CODE_ACTION_PREVIOUS,
R.string.spoken_description_action_previous)
mKeyCodeMap.put(Constants.CODE_EMOJI, R.string.spoken_description_emoji)
// Because the upper-case and lower-case mappings of the following letters is depending on
// the locale, the upper case descriptions should be defined here. The lower case
// descriptions are handled in {@link #getSpokenLetterDescriptionId(Context,int)}.
// U+0049: "I" LATIN CAPITAL LETTER I
// U+0069: "i" LATIN SMALL LETTER I
// U+0130: "İ" LATIN CAPITAL LETTER I WITH DOT ABOVE
// U+0131: "ı" LATIN SMALL LETTER DOTLESS I
mKeyCodeMap.put(0x0049, R.string.spoken_letter_0049)
mKeyCodeMap.put(0x0130, R.string.spoken_letter_0130)
}
} }

View file

@ -25,12 +25,14 @@ import org.dslul.openboard.inputmethod.keyboard.KeyboardView
* *
* @param <KV> The keyboard view class type. * @param <KV> The keyboard view class type.
</KV> */ </KV> */
open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyboardView: KV, protected val mKeyDetector: KeyDetector) : AccessibilityDelegateCompat() { open class KeyboardAccessibilityDelegate<KV : KeyboardView>(
protected val mKeyboardView: KV,
protected val mKeyDetector: KeyDetector
) : AccessibilityDelegateCompat() {
private var mKeyboard: Keyboard? = null private var mKeyboard: Keyboard? = null
private var mAccessibilityNodeProvider: KeyboardAccessibilityNodeProvider<KV>? = null private var mAccessibilityNodeProvider: KeyboardAccessibilityNodeProvider<KV>? = null
private var mLastHoverKey: Key? = null private var mLastHoverKey: Key? = null
protected open var lastHoverKey: Key? protected open var lastHoverKey: Key?
get() = mLastHoverKey get() = mLastHoverKey
set(key) { set(key) {
@ -63,7 +65,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
if (resId == 0) { if (resId == 0) {
return return
} }
val context = mKeyboardView!!.context val context = mKeyboardView.context
sendWindowStateChanged(context.getString(resId)) sendWindowStateChanged(context.getString(resId))
} }
@ -74,7 +76,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
*/ */
protected fun sendWindowStateChanged(text: String?) { protected fun sendWindowStateChanged(text: String?) {
val stateChange = AccessibilityUtils.obtainEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) val stateChange = AccessibilityUtils.obtainEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
mKeyboardView!!.onInitializeAccessibilityEvent(stateChange) mKeyboardView.onInitializeAccessibilityEvent(stateChange)
stateChange.text.add(text) stateChange.text.add(text)
stateChange.contentDescription = null stateChange.contentDescription = null
val parent = mKeyboardView.parent val parent = mKeyboardView.parent
@ -91,7 +93,8 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
*/ */
override fun getAccessibilityNodeProvider(host: View): KeyboardAccessibilityNodeProvider<KV> { override fun getAccessibilityNodeProvider(host: View): KeyboardAccessibilityNodeProvider<KV> {
return accessibilityNodeProvider return accessibilityNodeProvider
}// Instantiate the provide only when requested. Since the system }
// Instantiate the provide only when requested. Since the system
// will call this method multiple times it is a good practice to // will call this method multiple times it is a good practice to
// cache the provider instance. // cache the provider instance.
@ -99,7 +102,8 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
* @return A lazily-instantiated node provider for this view delegate. * @return A lazily-instantiated node provider for this view delegate.
*/ */
protected val accessibilityNodeProvider: KeyboardAccessibilityNodeProvider<KV> protected val accessibilityNodeProvider: KeyboardAccessibilityNodeProvider<KV>
get() { // Instantiate the provide only when requested. Since the system get() {
// Instantiate the provide only when requested. Since the system
// will call this method multiple times it is a good practice to // will call this method multiple times it is a good practice to
// cache the provider instance. // cache the provider instance.
return mAccessibilityNodeProvider ?: KeyboardAccessibilityNodeProvider(mKeyboardView, this) return mAccessibilityNodeProvider ?: KeyboardAccessibilityNodeProvider(mKeyboardView, this)
@ -208,7 +212,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
val eventTime = SystemClock.uptimeMillis() val eventTime = SystemClock.uptimeMillis()
val touchEvent = MotionEvent.obtain( val touchEvent = MotionEvent.obtain(
eventTime, eventTime, touchAction, x.toFloat(), y.toFloat(), 0 /* metaState */) eventTime, eventTime, touchAction, x.toFloat(), y.toFloat(), 0 /* metaState */)
mKeyboardView!!.onTouchEvent(touchEvent) mKeyboardView.onTouchEvent(touchEvent)
touchEvent.recycle() touchEvent.recycle()
} }
@ -222,7 +226,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
Log.d(TAG, "onHoverEnterTo: key=$key") Log.d(TAG, "onHoverEnterTo: key=$key")
} }
key.onPressed() key.onPressed()
mKeyboardView!!.invalidateKey(key) mKeyboardView.invalidateKey(key)
val provider = accessibilityNodeProvider val provider = accessibilityNodeProvider
provider.onHoverEnterTo(key) provider.onHoverEnterTo(key)
provider.performActionForKey(key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS) provider.performActionForKey(key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS)
@ -245,7 +249,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
Log.d(TAG, "onHoverExitFrom: key=$key") Log.d(TAG, "onHoverExitFrom: key=$key")
} }
key.onReleased() key.onReleased()
mKeyboardView!!.invalidateKey(key) mKeyboardView.invalidateKey(key)
val provider = accessibilityNodeProvider val provider = accessibilityNodeProvider
provider.onHoverExitFrom(key) provider.onHoverExitFrom(key)
} }
@ -255,7 +259,8 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
* *
* @param key A key to be long pressed on. * @param key A key to be long pressed on.
*/ */
open fun performLongClickOn(key: Key) { // A extended class should override this method to implement long press. open fun performLongClickOn(key: Key) {
// A extended class should override this method to implement long press.
} }
companion object { companion object {
@ -266,6 +271,6 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
init { init {
// Ensure that the view has an accessibility delegate. // Ensure that the view has an accessibility delegate.
ViewCompat.setAccessibilityDelegate(mKeyboardView!!, this) ViewCompat.setAccessibilityDelegate(mKeyboardView, this) // todo: see the warning, this may be bad
} }
} }

View file

@ -1,7 +1,6 @@
package org.dslul.openboard.inputmethod.accessibility package org.dslul.openboard.inputmethod.accessibility
import android.graphics.Rect import android.graphics.Rect
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
@ -28,10 +27,14 @@ import org.dslul.openboard.inputmethod.latin.settings.Settings
* virtual views, thus conveying their logical structure. * virtual views, thus conveying their logical structure.
* *
*/ */
class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV, class KeyboardAccessibilityNodeProvider<KV : KeyboardView>(
delegate: KeyboardAccessibilityDelegate<KV>) : AccessibilityNodeProviderCompat() { /** The keyboard view to provide an accessibility node info. */
private val mKeyCodeDescriptionMapper: KeyCodeDescriptionMapper private val mKeyboardView: KV,
private val mAccessibilityUtils: AccessibilityUtils /** The accessibility delegate. */
private val mDelegate: KeyboardAccessibilityDelegate<KV>
) : AccessibilityNodeProviderCompat() {
private val mKeyCodeDescriptionMapper: KeyCodeDescriptionMapper = KeyCodeDescriptionMapper.instance
private val mAccessibilityUtils: AccessibilityUtils = AccessibilityUtils.instance
/** Temporary rect used to calculate in-screen bounds. */ /** Temporary rect used to calculate in-screen bounds. */
private val mTempBoundsInScreen = Rect() private val mTempBoundsInScreen = Rect()
/** The parent view's cached on-screen location. */ /** The parent view's cached on-screen location. */
@ -40,12 +43,8 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
private var mAccessibilityFocusedView = UNDEFINED private var mAccessibilityFocusedView = UNDEFINED
/** The virtual view identifier for the hovering node. */ /** The virtual view identifier for the hovering node. */
private var mHoveringNodeId = UNDEFINED private var mHoveringNodeId = UNDEFINED
/** The keyboard view to provide an accessibility node info. */
private val mKeyboardView: KV
/** The accessibility delegate. */
private val mDelegate: KeyboardAccessibilityDelegate<KV>
/** The current keyboard. */ /** The current keyboard. */
private var mKeyboard: Keyboard? = null private var mKeyboard: Keyboard? = mKeyboardView.keyboard
/** /**
* Sets the keyboard represented by this node provider. * Sets the keyboard represented by this node provider.
@ -57,10 +56,8 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
} }
private fun getKeyOf(virtualViewId: Int): Key? { private fun getKeyOf(virtualViewId: Int): Key? {
if (mKeyboard == null) { val keyboard = mKeyboard ?: return null
return null val sortedKeys = keyboard.sortedKeys
}
val sortedKeys = mKeyboard!!.sortedKeys
// Use a virtual view id as an index of the sorted keys list. // Use a virtual view id as an index of the sorted keys list.
return if (virtualViewId >= 0 && virtualViewId < sortedKeys.size) { return if (virtualViewId >= 0 && virtualViewId < sortedKeys.size) {
sortedKeys[virtualViewId] sortedKeys[virtualViewId]
@ -68,10 +65,8 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
} }
private fun getVirtualViewIdOf(key: Key): Int { private fun getVirtualViewIdOf(key: Key): Int {
if (mKeyboard == null) { val keyboard = mKeyboard ?: return View.NO_ID
return View.NO_ID val sortedKeys = keyboard.sortedKeys
}
val sortedKeys = mKeyboard!!.sortedKeys
val size = sortedKeys.size val size = sortedKeys.size
for (index in 0 until size) { for (index in 0 until size) {
if (sortedKeys[index] === key) { // Use an index of the sorted keys list as a virtual view id. if (sortedKeys[index] === key) { // Use an index of the sorted keys list as a virtual view id.
@ -94,7 +89,7 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
val virtualViewId = getVirtualViewIdOf(key) val virtualViewId = getVirtualViewIdOf(key)
val keyDescription = getKeyDescription(key) val keyDescription = getKeyDescription(key)
val event = AccessibilityUtils.obtainEvent(eventType) val event = AccessibilityUtils.obtainEvent(eventType)
event.packageName = mKeyboardView!!.context.packageName event.packageName = mKeyboardView.context.packageName
event.className = key.javaClass.name event.className = key.javaClass.name
event.contentDescription = keyDescription event.contentDescription = keyDescription
event.isEnabled = true event.isEnabled = true
@ -152,13 +147,15 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
if (virtualViewId == UNDEFINED) { if (virtualViewId == UNDEFINED) {
return null return null
} }
if (virtualViewId == View.NO_ID) { // We are requested to create an AccessibilityNodeInfo describing val keyboard = mKeyboard ?: return null
if (virtualViewId == View.NO_ID) {
// We are requested to create an AccessibilityNodeInfo describing
// this View, i.e. the root of the virtual sub-tree. // this View, i.e. the root of the virtual sub-tree.
val rootInfo = AccessibilityNodeInfoCompat.obtain(mKeyboardView) val rootInfo = AccessibilityNodeInfoCompat.obtain(mKeyboardView)
ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView!!, rootInfo) ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, rootInfo)
updateParentLocation() updateParentLocation()
// Add the virtual children of the root View. // Add the virtual children of the root View.
val sortedKeys = mKeyboard!!.sortedKeys val sortedKeys = keyboard.sortedKeys
val size = sortedKeys.size val size = sortedKeys.size
for (index in 0 until size) { for (index in 0 until size) {
val key = sortedKeys[index] val key = sortedKeys[index]
@ -180,12 +177,11 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
val boundsInParent = key.hitBox val boundsInParent = key.hitBox
// Calculate the key's in-screen bounds. // Calculate the key's in-screen bounds.
mTempBoundsInScreen.set(boundsInParent) mTempBoundsInScreen.set(boundsInParent)
mTempBoundsInScreen.offset( mTempBoundsInScreen.offset(CoordinateUtils.x(mParentLocation), CoordinateUtils.y(mParentLocation))
CoordinateUtils.x(mParentLocation), CoordinateUtils.y(mParentLocation))
val boundsInScreen = mTempBoundsInScreen val boundsInScreen = mTempBoundsInScreen
// Obtain and initialize an AccessibilityNodeInfo with information about the virtual view. // Obtain and initialize an AccessibilityNodeInfo with information about the virtual view.
val info = AccessibilityNodeInfoCompat.obtain() val info = AccessibilityNodeInfoCompat.obtain()
info.packageName = mKeyboardView!!.context.packageName info.packageName = mKeyboardView.context.packageName
info.className = key.javaClass.name info.className = key.javaClass.name
info.contentDescription = keyDescription info.contentDescription = keyDescription
info.setBoundsInParent(boundsInParent) info.setBoundsInParent(boundsInParent)
@ -227,14 +223,12 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
return when (action) { return when (action) {
AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS -> { AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS -> {
mAccessibilityFocusedView = getVirtualViewIdOf(key) mAccessibilityFocusedView = getVirtualViewIdOf(key)
sendAccessibilityEventForKey( sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
true true
} }
AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS -> { AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS -> {
mAccessibilityFocusedView = UNDEFINED mAccessibilityFocusedView = UNDEFINED
sendAccessibilityEventForKey( sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)
key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)
true true
} }
AccessibilityNodeInfoCompat.ACTION_CLICK -> { AccessibilityNodeInfoCompat.ACTION_CLICK -> {
@ -269,14 +263,13 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
* @return The context-specific description of the key. * @return The context-specific description of the key.
*/ */
private fun getKeyDescription(key: Key): String? { private fun getKeyDescription(key: Key): String? {
val editorInfo = mKeyboard!!.mId.mEditorInfo val editorInfo = mKeyboard?.mId?.mEditorInfo
val shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo) val shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo)
val currentSettings = Settings.getInstance().current val currentSettings = Settings.getInstance().current
val keyCodeDescription = mKeyCodeDescriptionMapper.getDescriptionForKey( val keyCodeDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
mKeyboardView!!.context, mKeyboard, key, shouldObscure) mKeyboardView.context, mKeyboard, key, shouldObscure)
return if (currentSettings.isWordSeparator(key.code)) { return if (currentSettings.isWordSeparator(key.code)) {
mAccessibilityUtils.getAutoCorrectionDescription( mAccessibilityUtils.getAutoCorrectionDescription(keyCodeDescription, shouldObscure)
keyCodeDescription, shouldObscure)
} else keyCodeDescription } else keyCodeDescription
} }
@ -284,7 +277,7 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
* Updates the parent's on-screen location. * Updates the parent's on-screen location.
*/ */
private fun updateParentLocation() { private fun updateParentLocation() {
mKeyboardView!!.getLocationOnScreen(mParentLocation) mKeyboardView.getLocationOnScreen(mParentLocation)
} }
companion object { companion object {
@ -293,13 +286,4 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
private const val UNDEFINED = Int.MAX_VALUE private const val UNDEFINED = Int.MAX_VALUE
} }
init {
mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.instance
mAccessibilityUtils = AccessibilityUtils.instance
mKeyboardView = keyboardView
mDelegate = delegate
// Since this class is constructed lazily, we might not get a subsequent
// call to setKeyboard() and therefore need to call it now.
setKeyboard(keyboardView!!.keyboard)
}
} }

View file

@ -14,32 +14,17 @@ import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils
* This class represents a delegate that can be registered in [MainKeyboardView] to enhance * This class represents a delegate that can be registered in [MainKeyboardView] to enhance
* accessibility support via composition rather via inheritance. * accessibility support via composition rather via inheritance.
*/ */
class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView, class MainKeyboardAccessibilityDelegate(
keyDetector: KeyDetector) : KeyboardAccessibilityDelegate<MainKeyboardView?>(mainKeyboardView, keyDetector), LongPressTimerCallback { mainKeyboardView: MainKeyboardView,
companion object { keyDetector: KeyDetector
private val TAG = MainKeyboardAccessibilityDelegate::class.java.simpleName ) : KeyboardAccessibilityDelegate<MainKeyboardView>(mainKeyboardView, keyDetector), LongPressTimerCallback {
/** Map of keyboard modes to resource IDs. */
private val KEYBOARD_MODE_RES_IDS = SparseIntArray()
private const val KEYBOARD_IS_HIDDEN = -1
init {
KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATE, R.string.keyboard_mode_date)
KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATETIME, R.string.keyboard_mode_date_time)
KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_EMAIL, R.string.keyboard_mode_email)
KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_IM, R.string.keyboard_mode_im)
KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_NUMBER, R.string.keyboard_mode_number)
KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_PHONE, R.string.keyboard_mode_phone)
KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TEXT, R.string.keyboard_mode_text)
KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TIME, R.string.keyboard_mode_time)
KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url)
}
}
/** The most recently set keyboard mode. */ /** The most recently set keyboard mode. */
private var mLastKeyboardMode = KEYBOARD_IS_HIDDEN private var mLastKeyboardMode = KEYBOARD_IS_HIDDEN
// The rectangle region to ignore hover events. // The rectangle region to ignore hover events.
private val mBoundsToIgnoreHoverEvent = Rect() private val mBoundsToIgnoreHoverEvent = Rect()
private val mAccessibilityLongPressTimer: AccessibilityLongPressTimer// Since this method is called even when accessibility is off, make sure private val mAccessibilityLongPressTimer = AccessibilityLongPressTimer(this /* callback */, mainKeyboardView.context)
// Since this method is called even when accessibility is off, make sure
// to check the state before announcing anything. // to check the state before announcing anything.
// Announce the language name only when the language is changed. // Announce the language name only when the language is changed.
// Announce the mode only when the mode is changed. // Announce the mode only when the mode is changed.
@ -107,7 +92,7 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
* @param keyboard The new keyboard. * @param keyboard The new keyboard.
*/ */
private fun announceKeyboardMode(keyboard: Keyboard) { private fun announceKeyboardMode(keyboard: Keyboard) {
val context = mKeyboardView!!.context val context = mKeyboardView.context
val modeTextResId = KEYBOARD_MODE_RES_IDS[keyboard.mId.mMode] val modeTextResId = KEYBOARD_MODE_RES_IDS[keyboard.mId.mMode]
if (modeTextResId == 0) { if (modeTextResId == 0) {
return return
@ -129,21 +114,24 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
resId = when (keyboard.mId.mElementId) { resId = when (keyboard.mId.mElementId) {
KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardId.ELEMENT_ALPHABET -> { KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardId.ELEMENT_ALPHABET -> {
if (lastElementId == KeyboardId.ELEMENT_ALPHABET if (lastElementId == KeyboardId.ELEMENT_ALPHABET
|| lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { // Transition between alphabet mode and automatic shifted mode should be silently || lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
// Transition between alphabet mode and automatic shifted mode should be silently
// ignored because it can be determined by each key's talk back announce. // ignored because it can be determined by each key's talk back announce.
return return
} }
R.string.spoken_description_mode_alpha R.string.spoken_description_mode_alpha
} }
KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED -> { KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED -> {
if (lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { // Resetting automatic shifted mode by pressing the shift key causes the transition if (lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
// Resetting automatic shifted mode by pressing the shift key causes the transition
// from automatic shifted to manual shifted that should be silently ignored. // from automatic shifted to manual shifted that should be silently ignored.
return return
} }
R.string.spoken_description_shiftmode_on R.string.spoken_description_shiftmode_on
} }
KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED -> { KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED -> {
if (lastElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) { // Resetting caps locked mode by pressing the shift key causes the transition if (lastElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) {
// Resetting caps locked mode by pressing the shift key causes the transition
// from shift locked to shift lock shifted that should be silently ignored. // from shift locked to shift lock shifted that should be silently ignored.
return return
} }
@ -169,11 +157,12 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
override fun performClickOn(key: Key) { override fun performClickOn(key: Key) {
val x = key.hitBox.centerX() val x = key.hitBox.centerX()
val y = key.hitBox.centerY() val y = key.hitBox.centerY()
if (KeyboardAccessibilityDelegate.DEBUG_HOVER) { if (DEBUG_HOVER) {
Log.d(TAG, "performClickOn: key=" + key Log.d(TAG, "performClickOn: key=" + key
+ " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)) + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y))
} }
if (mBoundsToIgnoreHoverEvent.contains(x, y)) { // This hover exit event points to the key that should be ignored. if (mBoundsToIgnoreHoverEvent.contains(x, y)) {
// This hover exit event points to the key that should be ignored.
// Clear the ignoring region to handle further hover events. // Clear the ignoring region to handle further hover events.
mBoundsToIgnoreHoverEvent.setEmpty() mBoundsToIgnoreHoverEvent.setEmpty()
return return
@ -184,7 +173,7 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
override fun onHoverEnterTo(key: Key) { override fun onHoverEnterTo(key: Key) {
val x = key.hitBox.centerX() val x = key.hitBox.centerX()
val y = key.hitBox.centerY() val y = key.hitBox.centerY()
if (KeyboardAccessibilityDelegate.DEBUG_HOVER) { if (DEBUG_HOVER) {
Log.d(TAG, "onHoverEnterTo: key=" + key Log.d(TAG, "onHoverEnterTo: key=" + key
+ " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)) + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y))
} }
@ -204,7 +193,7 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
override fun onHoverExitFrom(key: Key) { override fun onHoverExitFrom(key: Key) {
val x = key.hitBox.centerX() val x = key.hitBox.centerX()
val y = key.hitBox.centerY() val y = key.hitBox.centerY()
if (KeyboardAccessibilityDelegate.DEBUG_HOVER) { if (DEBUG_HOVER) {
Log.d(TAG, "onHoverExitFrom: key=" + key Log.d(TAG, "onHoverExitFrom: key=" + key
+ " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)) + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y))
} }
@ -213,15 +202,14 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
} }
override fun performLongClickOn(key: Key) { override fun performLongClickOn(key: Key) {
if (KeyboardAccessibilityDelegate.Companion.DEBUG_HOVER) { if (DEBUG_HOVER) {
Log.d(TAG, "performLongClickOn: key=$key") Log.d(TAG, "performLongClickOn: key=$key")
} }
val tracker = PointerTracker.getPointerTracker(KeyboardAccessibilityDelegate.Companion.HOVER_EVENT_POINTER_ID) val tracker = PointerTracker.getPointerTracker(HOVER_EVENT_POINTER_ID)
val eventTime = SystemClock.uptimeMillis() val eventTime = SystemClock.uptimeMillis()
val x = key.hitBox.centerX() val x = key.hitBox.centerX()
val y = key.hitBox.centerY() val y = key.hitBox.centerY()
val downEvent = MotionEvent.obtain( val downEvent = MotionEvent.obtain(eventTime, eventTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat(), 0)
eventTime, eventTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat(), 0 /* metaState */)
// Inject a fake down event to {@link PointerTracker} to handle a long press correctly. // Inject a fake down event to {@link PointerTracker} to handle a long press correctly.
tracker.processMotionEvent(downEvent, mKeyDetector) tracker.processMotionEvent(downEvent, mKeyDetector)
downEvent.recycle() downEvent.recycle()
@ -231,7 +219,8 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
// or a key invokes IME switcher dialog, we should just ignore the next // or a key invokes IME switcher dialog, we should just ignore the next
// {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether // {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether
// {@link PointerTracker} is in operation or not. // {@link PointerTracker} is in operation or not.
if (tracker.isInOperation) { // This long press shows a more keys keyboard and further hover events should be if (tracker.isInOperation) {
// This long press shows a more keys keyboard and further hover events should be
// handled. // handled.
mBoundsToIgnoreHoverEvent.setEmpty() mBoundsToIgnoreHoverEvent.setEmpty()
return return
@ -239,17 +228,30 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
// This long press has handled at {@link MainKeyboardView#onLongPress(PointerTracker)}. // This long press has handled at {@link MainKeyboardView#onLongPress(PointerTracker)}.
// We should ignore further hover events on this key. // We should ignore further hover events on this key.
mBoundsToIgnoreHoverEvent.set(key.hitBox) mBoundsToIgnoreHoverEvent.set(key.hitBox)
if (key.hasNoPanelAutoMoreKey()) { // This long press has registered a code point without showing a more keys keyboard. if (key.hasNoPanelAutoMoreKey()) {
// This long press has registered a code point without showing a more keys keyboard.
// We should talk back the code point if possible. // We should talk back the code point if possible.
val codePointOfNoPanelAutoMoreKey = key.moreKeys!![0].mCode val codePointOfNoPanelAutoMoreKey = key.moreKeys?.get(0)?.mCode ?: return
val text: String = KeyCodeDescriptionMapper.instance.getDescriptionForCodePoint( val text: String = KeyCodeDescriptionMapper.instance.getDescriptionForCodePoint(
mKeyboardView!!.context, codePointOfNoPanelAutoMoreKey)!! mKeyboardView.context, codePointOfNoPanelAutoMoreKey) ?: return
text.let { sendWindowStateChanged(it) } sendWindowStateChanged(text)
} }
} }
init { companion object {
mAccessibilityLongPressTimer = AccessibilityLongPressTimer( private val TAG = MainKeyboardAccessibilityDelegate::class.java.simpleName
this /* callback */, mainKeyboardView.context) /** Map of keyboard modes to resource IDs. */
private val KEYBOARD_MODE_RES_IDS = SparseIntArray().apply {
put(KeyboardId.MODE_DATE, R.string.keyboard_mode_date)
put(KeyboardId.MODE_DATETIME, R.string.keyboard_mode_date_time)
put(KeyboardId.MODE_EMAIL, R.string.keyboard_mode_email)
put(KeyboardId.MODE_IM, R.string.keyboard_mode_im)
put(KeyboardId.MODE_NUMBER, R.string.keyboard_mode_number)
put(KeyboardId.MODE_PHONE, R.string.keyboard_mode_phone)
put(KeyboardId.MODE_TEXT, R.string.keyboard_mode_text)
put(KeyboardId.MODE_TIME, R.string.keyboard_mode_time)
put(KeyboardId.MODE_URL, R.string.keyboard_mode_url)
}
private const val KEYBOARD_IS_HIDDEN = -1
} }
} }

View file

@ -11,8 +11,10 @@ import org.dslul.openboard.inputmethod.keyboard.PointerTracker
* This class represents a delegate that can be registered in [MoreKeysKeyboardView] to * This class represents a delegate that can be registered in [MoreKeysKeyboardView] to
* enhance accessibility support via composition rather via inheritance. * enhance accessibility support via composition rather via inheritance.
*/ */
class MoreKeysKeyboardAccessibilityDelegate(moreKeysKeyboardView: MoreKeysKeyboardView, class MoreKeysKeyboardAccessibilityDelegate(
keyDetector: KeyDetector) : KeyboardAccessibilityDelegate<MoreKeysKeyboardView?>(moreKeysKeyboardView, keyDetector) { moreKeysKeyboardView: MoreKeysKeyboardView,
keyDetector: KeyDetector
) : KeyboardAccessibilityDelegate<MoreKeysKeyboardView>(moreKeysKeyboardView, keyDetector) {
private val mMoreKeysKeyboardValidBounds = Rect() private val mMoreKeysKeyboardValidBounds = Rect()
private var mOpenAnnounceResId = 0 private var mOpenAnnounceResId = 0
private var mCloseAnnounceResId = 0 private var mCloseAnnounceResId = 0
@ -42,7 +44,7 @@ class MoreKeysKeyboardAccessibilityDelegate(moreKeysKeyboardView: MoreKeysKeyboa
val y = event.getY(actionIndex).toInt() val y = event.getY(actionIndex).toInt()
val pointerId = event.getPointerId(actionIndex) val pointerId = event.getPointerId(actionIndex)
val eventTime = event.eventTime val eventTime = event.eventTime
mKeyboardView!!.onDownEvent(x, y, pointerId, eventTime) mKeyboardView.onDownEvent(x, y, pointerId, eventTime)
} }
override fun onHoverMove(event: MotionEvent) { override fun onHoverMove(event: MotionEvent) {
@ -52,7 +54,7 @@ class MoreKeysKeyboardAccessibilityDelegate(moreKeysKeyboardView: MoreKeysKeyboa
val y = event.getY(actionIndex).toInt() val y = event.getY(actionIndex).toInt()
val pointerId = event.getPointerId(actionIndex) val pointerId = event.getPointerId(actionIndex)
val eventTime = event.eventTime val eventTime = event.eventTime
mKeyboardView!!.onMoveEvent(x, y, pointerId, eventTime) mKeyboardView.onMoveEvent(x, y, pointerId, eventTime)
} }
override fun onHoverExit(event: MotionEvent) { override fun onHoverExit(event: MotionEvent) {
@ -71,9 +73,10 @@ class MoreKeysKeyboardAccessibilityDelegate(moreKeysKeyboardView: MoreKeysKeyboa
val eventTime = event.eventTime val eventTime = event.eventTime
// A hover exit event at one pixel width or height area on the edges of more keys keyboard // A hover exit event at one pixel width or height area on the edges of more keys keyboard
// are treated as closing. // are treated as closing.
mMoreKeysKeyboardValidBounds[0, 0, mKeyboardView!!.width] = mKeyboardView.height mMoreKeysKeyboardValidBounds[0, 0, mKeyboardView.width] = mKeyboardView.height
mMoreKeysKeyboardValidBounds.inset(CLOSING_INSET_IN_PIXEL, CLOSING_INSET_IN_PIXEL) mMoreKeysKeyboardValidBounds.inset(CLOSING_INSET_IN_PIXEL, CLOSING_INSET_IN_PIXEL)
if (mMoreKeysKeyboardValidBounds.contains(x, y)) { // Invoke {@link MoreKeysKeyboardView#onUpEvent(int,int,int,long)} as if this hover if (mMoreKeysKeyboardValidBounds.contains(x, y)) {
// Invoke {@link MoreKeysKeyboardView#onUpEvent(int,int,int,long)} as if this hover
// exit event selects a key. // exit event selects a key.
mKeyboardView.onUpEvent(x, y, pointerId, eventTime) mKeyboardView.onUpEvent(x, y, pointerId, eventTime)
// TODO: Should fix this reference. This is a hack to clear the state of // TODO: Should fix this reference. This is a hack to clear the state of