2023-10-17 13:44:01 +02:00
|
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2011 The Android Open Source Project
|
|
|
|
|
* modified
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
|
|
|
|
*/
|
|
|
|
|
|
2024-01-31 18:32:43 +01:00
|
|
|
|
package helium314.keyboard.accessibility
|
2020-01-21 18:16:01 +01:00
|
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.text.TextUtils
|
2024-01-31 18:32:43 +01:00
|
|
|
|
import helium314.keyboard.latin.utils.Log
|
2020-01-21 18:16:01 +01:00
|
|
|
|
import android.util.SparseIntArray
|
|
|
|
|
import android.view.inputmethod.EditorInfo
|
2024-01-31 18:32:43 +01:00
|
|
|
|
import helium314.keyboard.keyboard.Key
|
|
|
|
|
import helium314.keyboard.keyboard.Keyboard
|
|
|
|
|
import helium314.keyboard.keyboard.KeyboardId
|
|
|
|
|
import helium314.keyboard.latin.R
|
|
|
|
|
import helium314.keyboard.latin.common.Constants
|
|
|
|
|
import helium314.keyboard.latin.common.StringUtils
|
2020-01-21 18:16:01 +01:00
|
|
|
|
|
|
|
|
|
internal class KeyCodeDescriptionMapper private constructor() {
|
|
|
|
|
// Sparse array of spoken description resource IDs indexed by key codes
|
2023-09-16 20:12:47 +02:00
|
|
|
|
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)
|
|
|
|
|
}
|
2020-01-21 18:16:01 +01:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the localized description of the action performed by a specified
|
|
|
|
|
* key based on the current keyboard state.
|
|
|
|
|
*
|
|
|
|
|
* @param context The package's context.
|
|
|
|
|
* @param keyboard The keyboard on which the key resides.
|
|
|
|
|
* @param key The key from which to obtain a description.
|
|
|
|
|
* @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
|
|
|
|
|
*/
|
2023-09-16 20:12:47 +02:00
|
|
|
|
fun getDescriptionForKey(context: Context, keyboard: Keyboard?, key: Key, shouldObscure: Boolean): String? {
|
2020-01-21 18:16:01 +01:00
|
|
|
|
val code = key.code
|
2024-03-02 18:52:55 +01:00
|
|
|
|
if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL || code == Constants.CODE_SWITCH_SYMBOL || code == Constants.CODE_SWITCH_ALPHA) {
|
2020-01-21 18:16:01 +01:00
|
|
|
|
val description = getDescriptionForSwitchAlphaSymbol(context, keyboard)
|
|
|
|
|
if (description != null) {
|
|
|
|
|
return description
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (code == Constants.CODE_SHIFT) {
|
|
|
|
|
return getDescriptionForShiftKey(context, keyboard)
|
|
|
|
|
}
|
2023-09-16 20:12:47 +02:00
|
|
|
|
if (code == Constants.CODE_ENTER) {
|
|
|
|
|
// The following function returns the correct description in all action and
|
|
|
|
|
// regular enter cases, taking care of all modes.
|
2020-01-21 18:16:01 +01:00
|
|
|
|
return getDescriptionForActionKey(context, keyboard, key)
|
|
|
|
|
}
|
|
|
|
|
if (code == Constants.CODE_OUTPUT_TEXT) {
|
2024-01-18 14:56:27 +01:00
|
|
|
|
return key.outputText ?: context.getString(R.string.spoken_description_unknown)
|
2020-01-21 18:16:01 +01:00
|
|
|
|
}
|
|
|
|
|
// Just attempt to speak the description.
|
2023-09-16 20:12:47 +02:00
|
|
|
|
if (code != Constants.CODE_UNSPECIFIED) {
|
|
|
|
|
// If the key description should be obscured, now is the time to do it.
|
2020-01-21 18:16:01 +01:00
|
|
|
|
val isDefinedNonCtrl = (Character.isDefined(code)
|
|
|
|
|
&& !Character.isISOControl(code))
|
|
|
|
|
if (shouldObscure && isDefinedNonCtrl) {
|
|
|
|
|
return context.getString(OBSCURED_KEY_RES_ID)
|
|
|
|
|
}
|
|
|
|
|
val description = getDescriptionForCodePoint(context, code)
|
|
|
|
|
if (description != null) {
|
|
|
|
|
return description
|
|
|
|
|
}
|
|
|
|
|
return if (!TextUtils.isEmpty(key.label)) {
|
|
|
|
|
key.label
|
|
|
|
|
} else context.getString(R.string.spoken_description_unknown)
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a localized character sequence describing what will happen when
|
|
|
|
|
* the specified key is pressed based on its key code point.
|
|
|
|
|
*
|
|
|
|
|
* @param context The package's context.
|
|
|
|
|
* @param codePoint The code point from which to obtain a description.
|
|
|
|
|
* @return a character sequence describing the code point.
|
|
|
|
|
*/
|
2023-09-16 20:12:47 +02:00
|
|
|
|
fun getDescriptionForCodePoint(context: Context, codePoint: Int): String? {
|
|
|
|
|
// If the key description should be obscured, now is the time to do it.
|
2020-01-21 18:16:01 +01:00
|
|
|
|
val index = mKeyCodeMap.indexOfKey(codePoint)
|
|
|
|
|
if (index >= 0) {
|
|
|
|
|
return context.getString(mKeyCodeMap.valueAt(index))
|
|
|
|
|
}
|
|
|
|
|
return if (Character.isDefined(codePoint) && !Character.isISOControl(codePoint)) {
|
|
|
|
|
StringUtils.newSingleCodePointString(codePoint)
|
|
|
|
|
} else null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
private val TAG = KeyCodeDescriptionMapper::class.java.simpleName
|
|
|
|
|
// The resource ID of the string spoken for obscured keys
|
2023-09-26 11:34:32 +02:00
|
|
|
|
private val OBSCURED_KEY_RES_ID = R.string.spoken_description_dot
|
2020-01-21 18:16:01 +01:00
|
|
|
|
val instance = KeyCodeDescriptionMapper()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL
|
|
|
|
|
* key or `null` if there is not a description provided for the
|
|
|
|
|
* current keyboard context.
|
|
|
|
|
*
|
|
|
|
|
* @param context The package's context.
|
|
|
|
|
* @param keyboard The keyboard on which the key resides.
|
|
|
|
|
* @return a character sequence describing the action performed by pressing the key
|
|
|
|
|
*/
|
2023-09-16 20:12:47 +02:00
|
|
|
|
private fun getDescriptionForSwitchAlphaSymbol(context: Context, keyboard: Keyboard?): String? {
|
|
|
|
|
val resId = when (val elementId = keyboard?.mId?.mElementId) {
|
2020-01-21 18:16:01 +01:00
|
|
|
|
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
|
|
|
|
|
KeyboardId.ELEMENT_PHONE_SYMBOLS -> R.string.spoken_description_to_numeric
|
|
|
|
|
else -> {
|
|
|
|
|
Log.e(TAG, "Missing description for keyboard element ID:$elementId")
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return context.getString(resId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a context-sensitive description of the "Shift" key.
|
|
|
|
|
*
|
|
|
|
|
* @param context The package's context.
|
|
|
|
|
* @param keyboard The keyboard on which the key resides.
|
|
|
|
|
* @return A context-sensitive description of the "Shift" key.
|
|
|
|
|
*/
|
2023-09-16 20:12:47 +02:00
|
|
|
|
private fun getDescriptionForShiftKey(context: Context, keyboard: Keyboard?): String {
|
|
|
|
|
val resId: Int = when (keyboard?.mId?.mElementId) {
|
2020-01-21 18:16:01 +01:00
|
|
|
|
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
|
|
|
|
|
KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> R.string.spoken_description_symbols_shift_shifted
|
|
|
|
|
else -> R.string.spoken_description_shift
|
|
|
|
|
}
|
|
|
|
|
return context.getString(resId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a context-sensitive description of the "Enter" action key.
|
|
|
|
|
*
|
|
|
|
|
* @param context The package's context.
|
|
|
|
|
* @param keyboard The keyboard on which the key resides.
|
|
|
|
|
* @param key The key to describe.
|
|
|
|
|
* @return Returns a context-sensitive description of the "Enter" action key.
|
|
|
|
|
*/
|
2023-09-16 20:12:47 +02:00
|
|
|
|
private fun getDescriptionForActionKey(context: Context, keyboard: Keyboard?, key: Key): String {
|
2020-01-21 18:16:01 +01:00
|
|
|
|
// Always use the label, if available.
|
|
|
|
|
if (!TextUtils.isEmpty(key.label)) {
|
|
|
|
|
return key.label!!.trim { it <= ' ' }
|
|
|
|
|
}
|
2023-09-16 20:12:47 +02:00
|
|
|
|
val resId = when (keyboard?.mId?.imeAction()) {
|
2023-09-07 12:01:45 +02:00
|
|
|
|
EditorInfo.IME_ACTION_SEARCH -> R.string.label_search_key
|
2020-01-21 18:16:01 +01:00
|
|
|
|
EditorInfo.IME_ACTION_GO -> R.string.label_go_key
|
|
|
|
|
EditorInfo.IME_ACTION_SEND -> R.string.label_send_key
|
|
|
|
|
EditorInfo.IME_ACTION_NEXT -> R.string.label_next_key
|
|
|
|
|
EditorInfo.IME_ACTION_DONE -> R.string.label_done_key
|
|
|
|
|
EditorInfo.IME_ACTION_PREVIOUS -> R.string.label_previous_key
|
|
|
|
|
else -> R.string.spoken_description_return
|
|
|
|
|
}
|
|
|
|
|
return context.getString(resId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-16 20:12:47 +02:00
|
|
|
|
}
|