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
class AccessibilityUtils private constructor() {
private var mContext: Context? = null
private var mAccessibilityManager: AccessibilityManager? = null
private var mAudioManager: AudioManager? = null
private lateinit var mContext: Context
private lateinit var mAccessibilityManager: AccessibilityManager
private lateinit var mAudioManager: AudioManager
/** The most recent auto-correction. */
private var mAutoCorrectionWord: String? = null
/** The most recent typed word for auto-correction. */
@ -39,7 +39,7 @@ class AccessibilityUtils private constructor() {
* @return `true` if accessibility is enabled.
*/
val isAccessibilityEnabled: Boolean
get() = ENABLE_ACCESSIBILITY && mAccessibilityManager!!.isEnabled
get() = ENABLE_ACCESSIBILITY && mAccessibilityManager.isEnabled
/**
* Returns `true` if touch exploration is enabled. Currently, this
@ -49,7 +49,7 @@ class AccessibilityUtils private constructor() {
* @return `true` if touch exploration is enabled.
*/
val isTouchExplorationEnabled: Boolean
get() = isAccessibilityEnabled && mAccessibilityManager!!.isTouchExplorationEnabled
get() = isAccessibilityEnabled && mAccessibilityManager.isTouchExplorationEnabled
/**
* Returns whether the device should obscure typed password characters.
@ -61,12 +61,12 @@ class AccessibilityUtils private constructor() {
if (editorInfo == null) return false
// The user can optionally force speaking passwords.
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
if (speakPassword) return false
}
// Always speak if the user is listening through headphones.
return if (mAudioManager!!.isWiredHeadsetOn || mAudioManager!!.isBluetoothA2dpOn) {
return if (mAudioManager.isWiredHeadsetOn || mAudioManager.isBluetoothA2dpOn) {
false
} else InputTypeUtils.isPasswordInputType(editorInfo.inputType)
// 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.equals(mAutoCorrectionWord, mTypedWord)) {
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)
} else mContext!!.getString(R.string.spoken_auto_correct, keyCodeDescription,
} else mContext.getString(R.string.spoken_auto_correct, keyCodeDescription,
mTypedWord, mAutoCorrectionWord)
}
}
@ -122,7 +122,7 @@ class AccessibilityUtils private constructor() {
* @param text The text to speak.
*/
fun announceForAccessibility(view: View, text: CharSequence?) {
if (!mAccessibilityManager!!.isEnabled) {
if (!mAccessibilityManager.isEnabled) {
Log.e(TAG, "Attempted to speak when accessibility was disabled!")
return
}
@ -154,10 +154,9 @@ class AccessibilityUtils private constructor() {
* @param editorInfo The input connection's editor info attribute.
* @param restarting Whether the connection is being restarted.
*/
fun onStartInputViewInternal(view: View, editorInfo: EditorInfo?,
restarting: Boolean) {
fun onStartInputViewInternal(view: View, editorInfo: EditorInfo?, restarting: Boolean) {
if (shouldObscureInput(editorInfo)) {
val text = mContext!!.getText(R.string.spoken_use_headphones)
val text = mContext.getText(R.string.spoken_use_headphones)
announceForAccessibility(view, text)
}
}
@ -169,8 +168,8 @@ class AccessibilityUtils private constructor() {
* @param event The event to send.
*/
fun requestSendAccessibilityEvent(event: AccessibilityEvent?) {
if (mAccessibilityManager!!.isEnabled) {
mAccessibilityManager!!.sendAccessibilityEvent(event)
if (mAccessibilityManager.isEnabled) {
mAccessibilityManager.sendAccessibilityEvent(event)
}
}
@ -180,10 +179,10 @@ class AccessibilityUtils private constructor() {
private val PACKAGE = AccessibilityUtils::class.java.getPackage()!!.name
val instance = AccessibilityUtils()
/*
* Setting this constant to {@code false} will disable all keyboard
* accessibility code, regardless of whether Accessibility is turned on in
* the system settings. It should ONLY be used in the event of an emergency.
*/
* Setting this constant to {@code false} will disable all keyboard
* accessibility code, regardless of whether Accessibility is turned on in
* the system settings. It should ONLY be used in the event of an emergency.
*/
private const val ENABLE_ACCESSIBILITY = true
@JvmStatic

View file

@ -15,7 +15,30 @@ import java.util.*
internal class KeyCodeDescriptionMapper private constructor() {
// 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
@ -27,8 +50,7 @@ internal class KeyCodeDescriptionMapper private constructor() {
* @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
*/
fun getDescriptionForKey(context: Context, keyboard: Keyboard?,
key: Key, shouldObscure: Boolean): String? {
fun getDescriptionForKey(context: Context, keyboard: Keyboard?, key: Key, shouldObscure: Boolean): String? {
val code = key.code
if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
val description = getDescriptionForSwitchAlphaSymbol(context, keyboard)
@ -39,17 +61,19 @@ internal class KeyCodeDescriptionMapper private constructor() {
if (code == Constants.CODE_SHIFT) {
return getDescriptionForShiftKey(context, keyboard)
}
if (code == Constants.CODE_ENTER) { // The following function returns the correct description in all action and
// regular enter cases, taking care of all modes.
if (code == Constants.CODE_ENTER) {
// The following function returns the correct description in all action and
// regular enter cases, taking care of all modes.
return getDescriptionForActionKey(context, keyboard, key)
}
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)
return if (TextUtils.isEmpty(description)) outputText else 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)
&& !Character.isISOControl(code))
if (shouldObscure && isDefinedNonCtrl) {
@ -74,7 +98,8 @@ internal class KeyCodeDescriptionMapper private constructor() {
* @param codePoint The code point from which to obtain a description.
* @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)
if (index >= 0) {
return context.getString(mKeyCodeMap.valueAt(index))
@ -141,8 +166,7 @@ internal class KeyCodeDescriptionMapper private constructor() {
val resourceName = String.format(Locale.ROOT, resourceNameFormat, code)
val resources = context.resources
// Note that the resource package name may differ from the context package name.
val resourcePackageName = resources.getResourcePackageName(
R.string.spoken_description_unknown)
val resourcePackageName = resources.getResourcePackageName(R.string.spoken_description_unknown)
val resId = resources.getIdentifier(resourceName, "string", resourcePackageName)
if (resId != 0) {
mKeyCodeMap.append(code, resId)
@ -170,12 +194,8 @@ internal class KeyCodeDescriptionMapper private constructor() {
* @param keyboard The keyboard on which the key resides.
* @return a character sequence describing the action performed by pressing the key
*/
private fun getDescriptionForSwitchAlphaSymbol(context: Context,
keyboard: Keyboard?): String? {
val keyboardId = keyboard!!.mId
val elementId = keyboardId.mElementId
val resId: Int
resId = when (elementId) {
private fun getDescriptionForSwitchAlphaSymbol(context: Context, keyboard: Keyboard?): String? {
val resId = when (val elementId = keyboard?.mId?.mElementId) {
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_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.
* @return A context-sensitive description of the "Shift" key.
*/
private fun getDescriptionForShiftKey(context: Context,
keyboard: Keyboard?): String {
val keyboardId = keyboard!!.mId
val elementId = keyboardId.mElementId
val resId: Int
resId = when (elementId) {
private fun getDescriptionForShiftKey(context: Context, keyboard: Keyboard?): String {
val resId: Int = when (keyboard?.mId?.mElementId) {
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_SYMBOLS -> R.string.spoken_description_symbols_shift
@ -218,16 +234,12 @@ internal class KeyCodeDescriptionMapper private constructor() {
* @param key The key to describe.
* @return Returns a context-sensitive description of the "Enter" action key.
*/
private fun getDescriptionForActionKey(context: Context, keyboard: Keyboard?,
key: Key): String {
val keyboardId = keyboard!!.mId
val actionId = keyboardId.imeAction()
val resId: Int
private fun getDescriptionForActionKey(context: Context, keyboard: Keyboard?, key: Key): String {
// Always use the label, if available.
if (!TextUtils.isEmpty(key.label)) {
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_GO -> R.string.label_go_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.
private fun getSpokenEmoticonDescription(context: Context,
outputText: String?): String? {
private fun getSpokenEmoticonDescription(context: Context, outputText: String): String? {
val sb = StringBuilder(SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX)
val textLength = outputText!!.length
val textLength = outputText.length
var index = 0
while (index < textLength) {
val codePoint = outputText.codePointAt(index)
@ -253,36 +264,10 @@ internal class KeyCodeDescriptionMapper private constructor() {
val resourceName = sb.toString()
val resources = context.resources
// Note that the resource package name may differ from the context package name.
val resourcePackageName = resources.getResourcePackageName(
R.string.spoken_description_unknown)
val resourcePackageName = resources.getResourcePackageName(R.string.spoken_description_unknown)
val resId = resources.getIdentifier(resourceName, "string", resourcePackageName)
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.
</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 mAccessibilityNodeProvider: KeyboardAccessibilityNodeProvider<KV>? = null
private var mLastHoverKey: Key? = null
protected open var lastHoverKey: Key?
get() = mLastHoverKey
set(key) {
@ -45,14 +47,14 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
* @param keyboard The keyboard that is being set to the wrapping view.
*/
open var keyboard: Keyboard?
get() = mKeyboard
set(keyboard) {
if (keyboard == null) {
return
get() = mKeyboard
set(keyboard) {
if (keyboard == null) {
return
}
mAccessibilityNodeProvider?.setKeyboard(keyboard)
mKeyboard = keyboard
}
mAccessibilityNodeProvider?.setKeyboard(keyboard)
mKeyboard = keyboard
}
/**
* Sends a window state change event with the specified string resource id.
@ -63,7 +65,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
if (resId == 0) {
return
}
val context = mKeyboardView!!.context
val context = mKeyboardView.context
sendWindowStateChanged(context.getString(resId))
}
@ -74,7 +76,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
*/
protected fun sendWindowStateChanged(text: String?) {
val stateChange = AccessibilityUtils.obtainEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
mKeyboardView!!.onInitializeAccessibilityEvent(stateChange)
mKeyboardView.onInitializeAccessibilityEvent(stateChange)
stateChange.text.add(text)
stateChange.contentDescription = null
val parent = mKeyboardView.parent
@ -91,19 +93,21 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
*/
override fun getAccessibilityNodeProvider(host: View): KeyboardAccessibilityNodeProvider<KV> {
return accessibilityNodeProvider
}// Instantiate the provide only when requested. Since the system
// will call this method multiple times it is a good practice to
// cache the provider instance.
}
// Instantiate the provide only when requested. Since the system
// will call this method multiple times it is a good practice to
// cache the provider instance.
/**
* @return A lazily-instantiated node provider for this view delegate.
*/
protected val accessibilityNodeProvider: KeyboardAccessibilityNodeProvider<KV>
get() { // Instantiate the provide only when requested. Since the system
// will call this method multiple times it is a good practice to
// cache the provider instance.
return mAccessibilityNodeProvider ?: KeyboardAccessibilityNodeProvider(mKeyboardView, this)
}
get() {
// Instantiate the provide only when requested. Since the system
// will call this method multiple times it is a good practice to
// cache the provider instance.
return mAccessibilityNodeProvider ?: KeyboardAccessibilityNodeProvider(mKeyboardView, this)
}
/**
* Get a key that a hover event is on.
@ -177,7 +181,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
lastKey?.let { onHoverExitFrom(it) }
val key = getHoverKeyOf(event)
// Make sure we're not getting an EXIT event because the user slid
// off the keyboard area, then force a key press.
// off the keyboard area, then force a key press.
key?.let { performClickOn(it)
onHoverExitFrom(it) }
mLastHoverKey = null
@ -208,7 +212,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
val eventTime = SystemClock.uptimeMillis()
val touchEvent = MotionEvent.obtain(
eventTime, eventTime, touchAction, x.toFloat(), y.toFloat(), 0 /* metaState */)
mKeyboardView!!.onTouchEvent(touchEvent)
mKeyboardView.onTouchEvent(touchEvent)
touchEvent.recycle()
}
@ -222,7 +226,7 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
Log.d(TAG, "onHoverEnterTo: key=$key")
}
key.onPressed()
mKeyboardView!!.invalidateKey(key)
mKeyboardView.invalidateKey(key)
val provider = accessibilityNodeProvider
provider.onHoverEnterTo(key)
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")
}
key.onReleased()
mKeyboardView!!.invalidateKey(key)
mKeyboardView.invalidateKey(key)
val provider = accessibilityNodeProvider
provider.onHoverExitFrom(key)
}
@ -255,17 +259,18 @@ open class KeyboardAccessibilityDelegate<KV : KeyboardView?>(protected val mKeyb
*
* @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 {
private val TAG = KeyboardAccessibilityDelegate::class.java.simpleName
const val DEBUG_HOVER = false
const val HOVER_EVENT_POINTER_ID = 0
private val TAG = KeyboardAccessibilityDelegate::class.java.simpleName
const val DEBUG_HOVER = false
const val HOVER_EVENT_POINTER_ID = 0
}
init {
// 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
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
@ -28,10 +27,14 @@ import org.dslul.openboard.inputmethod.latin.settings.Settings
* virtual views, thus conveying their logical structure.
*
*/
class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
delegate: KeyboardAccessibilityDelegate<KV>) : AccessibilityNodeProviderCompat() {
private val mKeyCodeDescriptionMapper: KeyCodeDescriptionMapper
private val mAccessibilityUtils: AccessibilityUtils
class KeyboardAccessibilityNodeProvider<KV : KeyboardView>(
/** The keyboard view to provide an accessibility node info. */
private val mKeyboardView: KV,
/** 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. */
private val mTempBoundsInScreen = Rect()
/** The parent view's cached on-screen location. */
@ -40,12 +43,8 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
private var mAccessibilityFocusedView = UNDEFINED
/** The virtual view identifier for the hovering node. */
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. */
private var mKeyboard: Keyboard? = null
private var mKeyboard: Keyboard? = mKeyboardView.keyboard
/**
* Sets the keyboard represented by this node provider.
@ -57,10 +56,8 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
}
private fun getKeyOf(virtualViewId: Int): Key? {
if (mKeyboard == null) {
return null
}
val sortedKeys = mKeyboard!!.sortedKeys
val keyboard = mKeyboard ?: return null
val sortedKeys = keyboard.sortedKeys
// Use a virtual view id as an index of the sorted keys list.
return if (virtualViewId >= 0 && virtualViewId < sortedKeys.size) {
sortedKeys[virtualViewId]
@ -68,10 +65,8 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
}
private fun getVirtualViewIdOf(key: Key): Int {
if (mKeyboard == null) {
return View.NO_ID
}
val sortedKeys = mKeyboard!!.sortedKeys
val keyboard = mKeyboard ?: return View.NO_ID
val sortedKeys = keyboard.sortedKeys
val size = sortedKeys.size
for (index in 0 until size) {
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 keyDescription = getKeyDescription(key)
val event = AccessibilityUtils.obtainEvent(eventType)
event.packageName = mKeyboardView!!.context.packageName
event.packageName = mKeyboardView.context.packageName
event.className = key.javaClass.name
event.contentDescription = keyDescription
event.isEnabled = true
@ -109,8 +104,8 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
return
}
// Start hovering on the key. Because our accessibility model is lift-to-type, we should
// report the node info without click and long click actions to avoid unnecessary
// announcements.
// report the node info without click and long click actions to avoid unnecessary
// announcements.
mHoveringNodeId = id
// Invalidate the node info of the key.
sendAccessibilityEventForKey(key, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)
@ -120,7 +115,7 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
fun onHoverExitFrom(key: Key) {
mHoveringNodeId = UNDEFINED
// Invalidate the node info of the key to be able to revert the change we have done
// in {@link #onHoverEnterTo(Key)}.
// in {@link #onHoverEnterTo(Key)}.
sendAccessibilityEventForKey(key, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)
sendAccessibilityEventForKey(key, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT)
}
@ -152,13 +147,15 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
if (virtualViewId == UNDEFINED) {
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.
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.
val rootInfo = AccessibilityNodeInfoCompat.obtain(mKeyboardView)
ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView!!, rootInfo)
ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, rootInfo)
updateParentLocation()
// Add the virtual children of the root View.
val sortedKeys = mKeyboard!!.sortedKeys
val sortedKeys = keyboard.sortedKeys
val size = sortedKeys.size
for (index in 0 until size) {
val key = sortedKeys[index]
@ -180,12 +177,11 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
val boundsInParent = key.hitBox
// Calculate the key's in-screen bounds.
mTempBoundsInScreen.set(boundsInParent)
mTempBoundsInScreen.offset(
CoordinateUtils.x(mParentLocation), CoordinateUtils.y(mParentLocation))
mTempBoundsInScreen.offset(CoordinateUtils.x(mParentLocation), CoordinateUtils.y(mParentLocation))
val boundsInScreen = mTempBoundsInScreen
// Obtain and initialize an AccessibilityNodeInfo with information about the virtual view.
val info = AccessibilityNodeInfoCompat.obtain()
info.packageName = mKeyboardView!!.context.packageName
info.packageName = mKeyboardView.context.packageName
info.className = key.javaClass.name
info.contentDescription = keyDescription
info.setBoundsInParent(boundsInParent)
@ -195,7 +191,7 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
info.isEnabled = key.isEnabled
info.isVisibleToUser = true
// Don't add ACTION_CLICK and ACTION_LONG_CLOCK actions while hovering on the key.
// See {@link #onHoverEnterTo(Key)} and {@link #onHoverExitFrom(Key)}.
// See {@link #onHoverEnterTo(Key)} and {@link #onHoverExitFrom(Key)}.
if (virtualViewId != mHoveringNodeId) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK)
if (key.isLongPressEnabled) {
@ -227,14 +223,12 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
return when (action) {
AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS -> {
mAccessibilityFocusedView = getVirtualViewIdOf(key)
sendAccessibilityEventForKey(
key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
true
}
AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS -> {
mAccessibilityFocusedView = UNDEFINED
sendAccessibilityEventForKey(
key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)
sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)
true
}
AccessibilityNodeInfoCompat.ACTION_CLICK -> {
@ -269,14 +263,13 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
* @return The context-specific description of the key.
*/
private fun getKeyDescription(key: Key): String? {
val editorInfo = mKeyboard!!.mId.mEditorInfo
val editorInfo = mKeyboard?.mId?.mEditorInfo
val shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo)
val currentSettings = Settings.getInstance().current
val keyCodeDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
mKeyboardView!!.context, mKeyboard, key, shouldObscure)
mKeyboardView.context, mKeyboard, key, shouldObscure)
return if (currentSettings.isWordSeparator(key.code)) {
mAccessibilityUtils.getAutoCorrectionDescription(
keyCodeDescription, shouldObscure)
mAccessibilityUtils.getAutoCorrectionDescription(keyCodeDescription, shouldObscure)
} else keyCodeDescription
}
@ -284,7 +277,7 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
* Updates the parent's on-screen location.
*/
private fun updateParentLocation() {
mKeyboardView!!.getLocationOnScreen(mParentLocation)
mKeyboardView.getLocationOnScreen(mParentLocation)
}
companion object {
@ -293,13 +286,4 @@ class KeyboardAccessibilityNodeProvider<KV : KeyboardView?>(keyboardView: KV,
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,33 +14,18 @@ import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils
* This class represents a delegate that can be registered in [MainKeyboardView] to enhance
* accessibility support via composition rather via inheritance.
*/
class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
keyDetector: KeyDetector) : KeyboardAccessibilityDelegate<MainKeyboardView?>(mainKeyboardView, keyDetector), LongPressTimerCallback {
companion object {
private val TAG = MainKeyboardAccessibilityDelegate::class.java.simpleName
/** 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)
}
}
class MainKeyboardAccessibilityDelegate(
mainKeyboardView: MainKeyboardView,
keyDetector: KeyDetector
) : KeyboardAccessibilityDelegate<MainKeyboardView>(mainKeyboardView, keyDetector), LongPressTimerCallback {
/** The most recently set keyboard mode. */
private var mLastKeyboardMode = KEYBOARD_IS_HIDDEN
// The rectangle region to ignore hover events.
private val mBoundsToIgnoreHoverEvent = Rect()
private val mAccessibilityLongPressTimer: AccessibilityLongPressTimer// Since this method is called even when accessibility is off, make sure
// to check the state before announcing anything.
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.
// Announce the language name only when the language is changed.
// Announce the mode only when the mode is changed.
// Announce the keyboard type only when the type is changed.
@ -58,7 +43,7 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
val lastKeyboardMode = mLastKeyboardMode
mLastKeyboardMode = keyboard.mId.mMode
// 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.
if (!AccessibilityUtils.instance.isAccessibilityEnabled) {
return
}
@ -107,7 +92,7 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
* @param keyboard The new keyboard.
*/
private fun announceKeyboardMode(keyboard: Keyboard) {
val context = mKeyboardView!!.context
val context = mKeyboardView.context
val modeTextResId = KEYBOARD_MODE_RES_IDS[keyboard.mId.mMode]
if (modeTextResId == 0) {
return
@ -129,22 +114,25 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
resId = when (keyboard.mId.mElementId) {
KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, 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
// ignored because it can be determined by each key's talk back announce.
|| 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.
return
}
R.string.spoken_description_mode_alpha
}
KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED -> {
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.
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.
return
}
R.string.spoken_description_shiftmode_on
}
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
// from shift locked to shift lock shifted that should be silently ignored.
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.
return
}
R.string.spoken_description_shiftmode_locked
@ -169,12 +157,13 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
override fun performClickOn(key: Key) {
val x = key.hitBox.centerX()
val y = key.hitBox.centerY()
if (KeyboardAccessibilityDelegate.DEBUG_HOVER) {
if (DEBUG_HOVER) {
Log.d(TAG, "performClickOn: key=" + key
+ " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y))
}
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.
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.
mBoundsToIgnoreHoverEvent.setEmpty()
return
}
@ -184,7 +173,7 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
override fun onHoverEnterTo(key: Key) {
val x = key.hitBox.centerX()
val y = key.hitBox.centerY()
if (KeyboardAccessibilityDelegate.DEBUG_HOVER) {
if (DEBUG_HOVER) {
Log.d(TAG, "onHoverEnterTo: key=" + key
+ " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y))
}
@ -193,7 +182,7 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
return
}
// This hover enter event points to the key that isn't in the ignoring region.
// Further hover events should be handled.
// Further hover events should be handled.
mBoundsToIgnoreHoverEvent.setEmpty()
super.onHoverEnterTo(key)
if (key.isLongPressEnabled) {
@ -204,7 +193,7 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
override fun onHoverExitFrom(key: Key) {
val x = key.hitBox.centerX()
val y = key.hitBox.centerY()
if (KeyboardAccessibilityDelegate.DEBUG_HOVER) {
if (DEBUG_HOVER) {
Log.d(TAG, "onHoverExitFrom: key=" + key
+ " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y))
}
@ -213,43 +202,56 @@ class MainKeyboardAccessibilityDelegate(mainKeyboardView: MainKeyboardView,
}
override fun performLongClickOn(key: Key) {
if (KeyboardAccessibilityDelegate.Companion.DEBUG_HOVER) {
if (DEBUG_HOVER) {
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 x = key.hitBox.centerX()
val y = key.hitBox.centerY()
val downEvent = MotionEvent.obtain(
eventTime, eventTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat(), 0 /* metaState */)
val downEvent = MotionEvent.obtain(eventTime, eventTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat(), 0)
// Inject a fake down event to {@link PointerTracker} to handle a long press correctly.
tracker.processMotionEvent(downEvent, mKeyDetector)
downEvent.recycle()
// Invoke {@link PointerTracker#onLongPressed()} as if a long press timeout has passed.
tracker.onLongPressed()
// If {@link Key#hasNoPanelAutoMoreKeys()} is true (such as "0 +" key on the phone layout)
// or a key invokes IME switcher dialog, we should just ignore the next
// {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether
// {@link PointerTracker} is in operation or not.
if (tracker.isInOperation) { // This long press shows a more keys keyboard and further hover events should be
// handled.
// or a key invokes IME switcher dialog, we should just ignore the next
// {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether
// {@link PointerTracker} is in operation or not.
if (tracker.isInOperation) {
// This long press shows a more keys keyboard and further hover events should be
// handled.
mBoundsToIgnoreHoverEvent.setEmpty()
return
}
// 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)
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.
val codePointOfNoPanelAutoMoreKey = key.moreKeys!![0].mCode
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.
val codePointOfNoPanelAutoMoreKey = key.moreKeys?.get(0)?.mCode ?: return
val text: String = KeyCodeDescriptionMapper.instance.getDescriptionForCodePoint(
mKeyboardView!!.context, codePointOfNoPanelAutoMoreKey)!!
text.let { sendWindowStateChanged(it) }
mKeyboardView.context, codePointOfNoPanelAutoMoreKey) ?: return
sendWindowStateChanged(text)
}
}
init {
mAccessibilityLongPressTimer = AccessibilityLongPressTimer(
this /* callback */, mainKeyboardView.context)
companion object {
private val TAG = MainKeyboardAccessibilityDelegate::class.java.simpleName
/** 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
* enhance accessibility support via composition rather via inheritance.
*/
class MoreKeysKeyboardAccessibilityDelegate(moreKeysKeyboardView: MoreKeysKeyboardView,
keyDetector: KeyDetector) : KeyboardAccessibilityDelegate<MoreKeysKeyboardView?>(moreKeysKeyboardView, keyDetector) {
class MoreKeysKeyboardAccessibilityDelegate(
moreKeysKeyboardView: MoreKeysKeyboardView,
keyDetector: KeyDetector
) : KeyboardAccessibilityDelegate<MoreKeysKeyboardView>(moreKeysKeyboardView, keyDetector) {
private val mMoreKeysKeyboardValidBounds = Rect()
private var mOpenAnnounceResId = 0
private var mCloseAnnounceResId = 0
@ -42,7 +44,7 @@ class MoreKeysKeyboardAccessibilityDelegate(moreKeysKeyboardView: MoreKeysKeyboa
val y = event.getY(actionIndex).toInt()
val pointerId = event.getPointerId(actionIndex)
val eventTime = event.eventTime
mKeyboardView!!.onDownEvent(x, y, pointerId, eventTime)
mKeyboardView.onDownEvent(x, y, pointerId, eventTime)
}
override fun onHoverMove(event: MotionEvent) {
@ -52,7 +54,7 @@ class MoreKeysKeyboardAccessibilityDelegate(moreKeysKeyboardView: MoreKeysKeyboa
val y = event.getY(actionIndex).toInt()
val pointerId = event.getPointerId(actionIndex)
val eventTime = event.eventTime
mKeyboardView!!.onMoveEvent(x, y, pointerId, eventTime)
mKeyboardView.onMoveEvent(x, y, pointerId, eventTime)
}
override fun onHoverExit(event: MotionEvent) {
@ -70,20 +72,21 @@ class MoreKeysKeyboardAccessibilityDelegate(moreKeysKeyboardView: MoreKeysKeyboa
val pointerId = event.getPointerId(actionIndex)
val eventTime = event.eventTime
// A hover exit event at one pixel width or height area on the edges of more keys keyboard
// are treated as closing.
mMoreKeysKeyboardValidBounds[0, 0, mKeyboardView!!.width] = mKeyboardView.height
// are treated as closing.
mMoreKeysKeyboardValidBounds[0, 0, mKeyboardView.width] = mKeyboardView.height
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
// exit event selects a key.
if (mMoreKeysKeyboardValidBounds.contains(x, y)) {
// Invoke {@link MoreKeysKeyboardView#onUpEvent(int,int,int,long)} as if this hover
// exit event selects a key.
mKeyboardView.onUpEvent(x, y, pointerId, eventTime)
// TODO: Should fix this reference. This is a hack to clear the state of
// {@link PointerTracker}.
// {@link PointerTracker}.
PointerTracker.dismissAllMoreKeysPanels()
return
}
// Close the more keys keyboard.
// TODO: Should fix this reference. This is a hack to clear the state of
// {@link PointerTracker}.
// TODO: Should fix this reference. This is a hack to clear the state of
// {@link PointerTracker}.
PointerTracker.dismissAllMoreKeysPanels()
}