diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityLongPressTimer.java b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityLongPressTimer.java
deleted file mode 100644
index 3843b26b..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityLongPressTimer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.dslul.openboard.inputmethod.accessibility;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-
-import org.dslul.openboard.inputmethod.keyboard.Key;
-import org.dslul.openboard.inputmethod.latin.R;
-
-// Handling long press timer to show a more keys keyboard.
-final class AccessibilityLongPressTimer extends Handler {
- public interface LongPressTimerCallback {
- public void performLongClickOn(Key key);
- }
-
- private static final int MSG_LONG_PRESS = 1;
-
- private final LongPressTimerCallback mCallback;
- private final long mConfigAccessibilityLongPressTimeout;
-
- public AccessibilityLongPressTimer(final LongPressTimerCallback callback,
- final Context context) {
- super();
- mCallback = callback;
- mConfigAccessibilityLongPressTimeout = context.getResources().getInteger(
- R.integer.config_accessibility_long_press_key_timeout);
- }
-
- @Override
- public void handleMessage(final Message msg) {
- switch (msg.what) {
- case MSG_LONG_PRESS:
- cancelLongPress();
- mCallback.performLongClickOn((Key)msg.obj);
- return;
- default:
- super.handleMessage(msg);
- return;
- }
- }
-
- public void startLongPress(final Key key) {
- cancelLongPress();
- final Message longPressMessage = obtainMessage(MSG_LONG_PRESS, key);
- sendMessageDelayed(longPressMessage, mConfigAccessibilityLongPressTimeout);
- }
-
- public void cancelLongPress() {
- removeMessages(MSG_LONG_PRESS);
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityLongPressTimer.kt b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityLongPressTimer.kt
new file mode 100644
index 00000000..77c546d6
--- /dev/null
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityLongPressTimer.kt
@@ -0,0 +1,49 @@
+package org.dslul.openboard.inputmethod.accessibility
+
+import android.content.Context
+import android.os.Handler
+import android.os.Message
+import org.dslul.openboard.inputmethod.keyboard.Key
+import org.dslul.openboard.inputmethod.latin.R
+
+// Handling long press timer to show a more keys keyboard.
+internal class AccessibilityLongPressTimer(private val mCallback: LongPressTimerCallback,
+ context: Context) : Handler() {
+ interface LongPressTimerCallback {
+ fun performLongClickOn(key: Key)
+ }
+
+ private val mConfigAccessibilityLongPressTimeout: Long
+ override fun handleMessage(msg: Message) {
+ when (msg.what) {
+ MSG_LONG_PRESS -> {
+ cancelLongPress()
+ mCallback.performLongClickOn(msg.obj as Key)
+ return
+ }
+ else -> {
+ super.handleMessage(msg)
+ return
+ }
+ }
+ }
+
+ fun startLongPress(key: Key?) {
+ cancelLongPress()
+ val longPressMessage = obtainMessage(MSG_LONG_PRESS, key)
+ sendMessageDelayed(longPressMessage, mConfigAccessibilityLongPressTimeout)
+ }
+
+ fun cancelLongPress() {
+ removeMessages(MSG_LONG_PRESS)
+ }
+
+ companion object {
+ private const val MSG_LONG_PRESS = 1
+ }
+
+ init {
+ mConfigAccessibilityLongPressTimeout = context.resources.getInteger(
+ R.integer.config_accessibility_long_press_key_timeout).toLong()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityUtils.java
deleted file mode 100644
index 4bcbc4c1..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityUtils.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.dslul.openboard.inputmethod.accessibility;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.SystemClock;
-import android.provider.Settings;
-import androidx.core.view.accessibility.AccessibilityEventCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.inputmethod.EditorInfo;
-
-import org.dslul.openboard.inputmethod.compat.SettingsSecureCompatUtils;
-import org.dslul.openboard.inputmethod.latin.R;
-import org.dslul.openboard.inputmethod.latin.SuggestedWords;
-import org.dslul.openboard.inputmethod.latin.utils.InputTypeUtils;
-
-public final class AccessibilityUtils {
- private static final String TAG = AccessibilityUtils.class.getSimpleName();
- private static final String CLASS = AccessibilityUtils.class.getName();
- private static final String PACKAGE =
- AccessibilityUtils.class.getPackage().getName();
-
- private static final AccessibilityUtils sInstance = new AccessibilityUtils();
-
- private Context mContext;
- private AccessibilityManager mAccessibilityManager;
- private AudioManager mAudioManager;
-
- /** The most recent auto-correction. */
- private String mAutoCorrectionWord;
-
- /** The most recent typed word for auto-correction. */
- private String mTypedWord;
-
- /*
- * 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 static final boolean ENABLE_ACCESSIBILITY = true;
-
- public static void init(final Context context) {
- if (!ENABLE_ACCESSIBILITY) return;
-
- // These only need to be initialized if the kill switch is off.
- sInstance.initInternal(context);
- }
-
- public static AccessibilityUtils getInstance() {
- return sInstance;
- }
-
- private AccessibilityUtils() {
- // This class is not publicly instantiable.
- }
-
- private void initInternal(final Context context) {
- mContext = context;
- mAccessibilityManager =
- (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- }
-
- /**
- * Returns {@code true} if accessibility is enabled. Currently, this means
- * that the kill switch is off and system accessibility is turned on.
- *
- * @return {@code true} if accessibility is enabled.
- */
- public boolean isAccessibilityEnabled() {
- return ENABLE_ACCESSIBILITY && mAccessibilityManager.isEnabled();
- }
-
- /**
- * Returns {@code true} if touch exploration is enabled. Currently, this
- * means that the kill switch is off, the device supports touch exploration,
- * and system accessibility is turned on.
- *
- * @return {@code true} if touch exploration is enabled.
- */
- public boolean isTouchExplorationEnabled() {
- return isAccessibilityEnabled() && mAccessibilityManager.isTouchExplorationEnabled();
- }
-
- /**
- * Returns {@true} if the provided event is a touch exploration (e.g. hover)
- * event. This is used to determine whether the event should be processed by
- * the touch exploration code within the keyboard.
- *
- * @param event The event to check.
- * @return {@true} is the event is a touch exploration event
- */
- public static boolean isTouchExplorationEvent(final MotionEvent event) {
- final int action = event.getAction();
- return action == MotionEvent.ACTION_HOVER_ENTER
- || action == MotionEvent.ACTION_HOVER_EXIT
- || action == MotionEvent.ACTION_HOVER_MOVE;
- }
-
- /**
- * Returns whether the device should obscure typed password characters.
- * Typically this means speaking "dot" in place of non-control characters.
- *
- * @return {@code true} if the device should obscure password characters.
- */
- @SuppressWarnings("deprecation")
- public boolean shouldObscureInput(final EditorInfo editorInfo) {
- if (editorInfo == null) return false;
-
- // The user can optionally force speaking passwords.
- if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) {
- final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(),
- SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
- if (speakPassword) return false;
- }
-
- // Always speak if the user is listening through headphones.
- if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) {
- return false;
- }
-
- // Don't speak if the IME is connected to a password field.
- return InputTypeUtils.isPasswordInputType(editorInfo.inputType);
- }
-
- /**
- * Sets the current auto-correction word and typed word. These may be used
- * to provide the user with a spoken description of what auto-correction
- * will occur when a key is typed.
- *
- * @param suggestedWords the list of suggested auto-correction words
- */
- public void setAutoCorrection(final SuggestedWords suggestedWords) {
- if (suggestedWords.mWillAutoCorrect) {
- mAutoCorrectionWord = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
- final SuggestedWords.SuggestedWordInfo typedWordInfo = suggestedWords.mTypedWordInfo;
- if (null == typedWordInfo) {
- mTypedWord = null;
- } else {
- mTypedWord = typedWordInfo.mWord;
- }
- } else {
- mAutoCorrectionWord = null;
- mTypedWord = null;
- }
- }
-
- /**
- * Obtains a description for an auto-correction key, taking into account the
- * currently typed word and auto-correction.
- *
- * @param keyCodeDescription spoken description of the key that will insert
- * an auto-correction
- * @param shouldObscure whether the key should be obscured
- * @return a description including a description of the auto-correction, if
- * needed
- */
- public String getAutoCorrectionDescription(
- final String keyCodeDescription, final boolean shouldObscure) {
- if (!TextUtils.isEmpty(mAutoCorrectionWord)) {
- if (!TextUtils.equals(mAutoCorrectionWord, mTypedWord)) {
- if (shouldObscure) {
- // This should never happen, but just in case...
- return mContext.getString(R.string.spoken_auto_correct_obscured,
- keyCodeDescription);
- }
- return mContext.getString(R.string.spoken_auto_correct, keyCodeDescription,
- mTypedWord, mAutoCorrectionWord);
- }
- }
-
- return keyCodeDescription;
- }
-
- /**
- * Sends the specified text to the {@link AccessibilityManager} to be
- * spoken.
- *
- * @param view The source view.
- * @param text The text to speak.
- */
- public void announceForAccessibility(final View view, final CharSequence text) {
- if (!mAccessibilityManager.isEnabled()) {
- Log.e(TAG, "Attempted to speak when accessibility was disabled!");
- return;
- }
-
- // The following is a hack to avoid using the heavy-weight TextToSpeech
- // class. Instead, we're just forcing a fake AccessibilityEvent into
- // the screen reader to make it speak.
- final AccessibilityEvent event = AccessibilityEvent.obtain();
-
- event.setPackageName(PACKAGE);
- event.setClassName(CLASS);
- event.setEventTime(SystemClock.uptimeMillis());
- event.setEnabled(true);
- event.getText().add(text);
-
- // Platforms starting at SDK version 16 (Build.VERSION_CODES.JELLY_BEAN) should use
- // announce events.
- event.setEventType(AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
-
- final ViewParent viewParent = view.getParent();
- if ((viewParent == null) || !(viewParent instanceof ViewGroup)) {
- Log.e(TAG, "Failed to obtain ViewParent in announceForAccessibility");
- return;
- }
-
- viewParent.requestSendAccessibilityEvent(view, event);
- }
-
- /**
- * Handles speaking the "connect a headset to hear passwords" notification
- * when connecting to a password field.
- *
- * @param view The source view.
- * @param editorInfo The input connection's editor info attribute.
- * @param restarting Whether the connection is being restarted.
- */
- public void onStartInputViewInternal(final View view, final EditorInfo editorInfo,
- final boolean restarting) {
- if (shouldObscureInput(editorInfo)) {
- final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
- announceForAccessibility(view, text);
- }
- }
-
- /**
- * Sends the specified {@link AccessibilityEvent} if accessibility is
- * enabled. No operation if accessibility is disabled.
- *
- * @param event The event to send.
- */
- public void requestSendAccessibilityEvent(final AccessibilityEvent event) {
- if (mAccessibilityManager.isEnabled()) {
- mAccessibilityManager.sendAccessibilityEvent(event);
- }
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityUtils.kt b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityUtils.kt
new file mode 100644
index 00000000..0ec3d308
--- /dev/null
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/AccessibilityUtils.kt
@@ -0,0 +1,210 @@
+package org.dslul.openboard.inputmethod.accessibility
+
+import android.content.Context
+import android.media.AudioManager
+import android.os.SystemClock
+import android.provider.Settings
+import android.text.TextUtils
+import android.util.Log
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import android.view.inputmethod.EditorInfo
+import androidx.core.view.accessibility.AccessibilityEventCompat
+import org.dslul.openboard.inputmethod.compat.SettingsSecureCompatUtils
+import org.dslul.openboard.inputmethod.latin.R
+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
+ /** The most recent auto-correction. */
+ private var mAutoCorrectionWord: String? = null
+ /** The most recent typed word for auto-correction. */
+ private var mTypedWord: String? = null
+
+ private fun initInternal(context: Context) {
+ mContext = context
+ mAccessibilityManager = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+ mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ }
+
+ /**
+ * Returns `true` if accessibility is enabled. Currently, this means
+ * that the kill switch is off and system accessibility is turned on.
+ *
+ * @return `true` if accessibility is enabled.
+ */
+ val isAccessibilityEnabled: Boolean
+ get() = ENABLE_ACCESSIBILITY && mAccessibilityManager!!.isEnabled
+
+ /**
+ * Returns `true` if touch exploration is enabled. Currently, this
+ * means that the kill switch is off, the device supports touch exploration,
+ * and system accessibility is turned on.
+ *
+ * @return `true` if touch exploration is enabled.
+ */
+ val isTouchExplorationEnabled: Boolean
+ get() = isAccessibilityEnabled && mAccessibilityManager!!.isTouchExplorationEnabled
+
+ /**
+ * Returns whether the device should obscure typed password characters.
+ * Typically this means speaking "dot" in place of non-control characters.
+ *
+ * @return `true` if the device should obscure password characters.
+ */
+ fun shouldObscureInput(editorInfo: EditorInfo?): Boolean {
+ if (editorInfo == null) return false
+ // The user can optionally force speaking passwords.
+ if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) {
+ val speakPassword = Settings.Secure.getInt(mContext!!.contentResolver,
+ SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0
+ if (speakPassword) return false
+ }
+ // Always speak if the user is listening through headphones.
+ return if (mAudioManager!!.isWiredHeadsetOn || mAudioManager!!.isBluetoothA2dpOn) {
+ false
+ } else InputTypeUtils.isPasswordInputType(editorInfo.inputType)
+ // Don't speak if the IME is connected to a password field.
+ }
+
+ /**
+ * Sets the current auto-correction word and typed word. These may be used
+ * to provide the user with a spoken description of what auto-correction
+ * will occur when a key is typed.
+ *
+ * @param suggestedWords the list of suggested auto-correction words
+ */
+ fun setAutoCorrection(suggestedWords: SuggestedWords) {
+ if (suggestedWords.mWillAutoCorrect) {
+ mAutoCorrectionWord = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION)
+ val typedWordInfo = suggestedWords.mTypedWordInfo
+ mTypedWord = typedWordInfo?.mWord
+ } else {
+ mAutoCorrectionWord = null
+ mTypedWord = null
+ }
+ }
+
+ /**
+ * Obtains a description for an auto-correction key, taking into account the
+ * currently typed word and auto-correction.
+ *
+ * @param keyCodeDescription spoken description of the key that will insert
+ * an auto-correction
+ * @param shouldObscure whether the key should be obscured
+ * @return a description including a description of the auto-correction, if
+ * needed
+ */
+ fun getAutoCorrectionDescription(
+ keyCodeDescription: String?, shouldObscure: Boolean): String? {
+ 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,
+ keyCodeDescription)
+ } else mContext!!.getString(R.string.spoken_auto_correct, keyCodeDescription,
+ mTypedWord, mAutoCorrectionWord)
+ }
+ }
+ return keyCodeDescription
+ }
+
+ /**
+ * Sends the specified text to the [AccessibilityManager] to be
+ * spoken.
+ *
+ * @param view The source view.
+ * @param text The text to speak.
+ */
+ fun announceForAccessibility(view: View, text: CharSequence?) {
+ if (!mAccessibilityManager!!.isEnabled) {
+ Log.e(TAG, "Attempted to speak when accessibility was disabled!")
+ return
+ }
+ // The following is a hack to avoid using the heavy-weight TextToSpeech
+// class. Instead, we're just forcing a fake AccessibilityEvent into
+// the screen reader to make it speak.
+ val event = AccessibilityEvent.obtain()
+ event.packageName = PACKAGE
+ event.className = CLASS
+ event.eventTime = SystemClock.uptimeMillis()
+ event.isEnabled = true
+ event.text.add(text)
+ // Platforms starting at SDK version 16 (Build.VERSION_CODES.JELLY_BEAN) should use
+// announce events.
+ event.eventType = AccessibilityEventCompat.TYPE_ANNOUNCEMENT
+ val viewParent = view.parent
+ if (viewParent == null || viewParent !is ViewGroup) {
+ Log.e(TAG, "Failed to obtain ViewParent in announceForAccessibility")
+ return
+ }
+ viewParent.requestSendAccessibilityEvent(view, event)
+ }
+
+ /**
+ * Handles speaking the "connect a headset to hear passwords" notification
+ * when connecting to a password field.
+ *
+ * @param view The source view.
+ * @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) {
+ if (shouldObscureInput(editorInfo)) {
+ val text = mContext!!.getText(R.string.spoken_use_headphones)
+ announceForAccessibility(view, text)
+ }
+ }
+
+ /**
+ * Sends the specified [AccessibilityEvent] if accessibility is
+ * enabled. No operation if accessibility is disabled.
+ *
+ * @param event The event to send.
+ */
+ fun requestSendAccessibilityEvent(event: AccessibilityEvent?) {
+ if (mAccessibilityManager!!.isEnabled) {
+ mAccessibilityManager!!.sendAccessibilityEvent(event)
+ }
+ }
+
+ companion object {
+ private val TAG = AccessibilityUtils::class.java.simpleName
+ private val CLASS = AccessibilityUtils::class.java.name
+ private val PACKAGE = AccessibilityUtils::class.java.getPackage()!!.name
+ public 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.
+ */
+ private const val ENABLE_ACCESSIBILITY = true
+
+ @JvmStatic
+ fun init(context: Context) {
+ if (!ENABLE_ACCESSIBILITY) return
+ // These only need to be initialized if the kill switch is off.
+ instance.initInternal(context)
+ }
+
+ /**
+ * Returns {@true} if the provided event is a touch exploration (e.g. hover)
+ * event. This is used to determine whether the event should be processed by
+ * the touch exploration code within the keyboard.
+ *
+ * @param event The event to check.
+ * @return {@true} is the event is a touch exploration event
+ */
+ fun isTouchExplorationEvent(event: MotionEvent): Boolean {
+ val action = event.action
+ return action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_EXIT || action == MotionEvent.ACTION_HOVER_MOVE
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/KeyCodeDescriptionMapper.java
deleted file mode 100644
index 903e073f..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.dslul.openboard.inputmethod.accessibility;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseIntArray;
-import android.view.inputmethod.EditorInfo;
-
-import org.dslul.openboard.inputmethod.keyboard.Key;
-import org.dslul.openboard.inputmethod.keyboard.Keyboard;
-import org.dslul.openboard.inputmethod.keyboard.KeyboardId;
-import org.dslul.openboard.inputmethod.latin.R;
-import org.dslul.openboard.inputmethod.latin.common.Constants;
-import org.dslul.openboard.inputmethod.latin.common.StringUtils;
-
-import java.util.Locale;
-
-final class KeyCodeDescriptionMapper {
- private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName();
- private static final String SPOKEN_LETTER_RESOURCE_NAME_FORMAT = "spoken_accented_letter_%04X";
- private static final String SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT = "spoken_symbol_%04X";
- private static final String SPOKEN_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X";
- private static final String SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX = "spoken_emoticon";
- private static final String SPOKEN_EMOTICON_CODE_POINT_FORMAT = "_%02X";
-
- // The resource ID of the string spoken for obscured keys
- private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot;
-
- private static final KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
-
- public static KeyCodeDescriptionMapper getInstance() {
- return sInstance;
- }
-
- // Sparse array of spoken description resource IDs indexed by key codes
- private final SparseIntArray mKeyCodeMap = new SparseIntArray();
-
- private KeyCodeDescriptionMapper() {
- // 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);
- }
-
- /**
- * 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
- */
- public String getDescriptionForKey(final Context context, final Keyboard keyboard,
- final Key key, final boolean shouldObscure) {
- final int code = key.getCode();
-
- if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
- final String description = getDescriptionForSwitchAlphaSymbol(context, keyboard);
- if (description != null) {
- return description;
- }
- }
-
- 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.
- return getDescriptionForActionKey(context, keyboard, key);
- }
-
- if (code == Constants.CODE_OUTPUT_TEXT) {
- final String outputText = key.getOutputText();
- final String description = getSpokenEmoticonDescription(context, outputText);
- return TextUtils.isEmpty(description) ? outputText : 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.
- final boolean isDefinedNonCtrl = Character.isDefined(code)
- && !Character.isISOControl(code);
- if (shouldObscure && isDefinedNonCtrl) {
- return context.getString(OBSCURED_KEY_RES_ID);
- }
- final String description = getDescriptionForCodePoint(context, code);
- if (description != null) {
- return description;
- }
- if (!TextUtils.isEmpty(key.getLabel())) {
- return key.getLabel();
- }
- return context.getString(R.string.spoken_description_unknown);
- }
- return null;
- }
-
- /**
- * Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL
- * key or {@code 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
- */
- private static String getDescriptionForSwitchAlphaSymbol(final Context context,
- final Keyboard keyboard) {
- final KeyboardId keyboardId = keyboard.mId;
- final int elementId = keyboardId.mElementId;
- final int resId;
-
- switch (elementId) {
- case KeyboardId.ELEMENT_ALPHABET:
- case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
- case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
- case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
- case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
- resId = R.string.spoken_description_to_symbol;
- break;
- case KeyboardId.ELEMENT_SYMBOLS:
- case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
- resId = R.string.spoken_description_to_alpha;
- break;
- case KeyboardId.ELEMENT_PHONE:
- resId = R.string.spoken_description_to_symbol;
- break;
- case KeyboardId.ELEMENT_PHONE_SYMBOLS:
- resId = R.string.spoken_description_to_numeric;
- break;
- default:
- 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.
- */
- private static String getDescriptionForShiftKey(final Context context,
- final Keyboard keyboard) {
- final KeyboardId keyboardId = keyboard.mId;
- final int elementId = keyboardId.mElementId;
- final int resId;
-
- switch (elementId) {
- case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
- case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
- resId = R.string.spoken_description_caps_lock;
- break;
- case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
- case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
- resId = R.string.spoken_description_shift_shifted;
- break;
- case KeyboardId.ELEMENT_SYMBOLS:
- resId = R.string.spoken_description_symbols_shift;
- break;
- case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
- resId = R.string.spoken_description_symbols_shift_shifted;
- break;
- default:
- resId = 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.
- */
- private static String getDescriptionForActionKey(final Context context, final Keyboard keyboard,
- final Key key) {
- final KeyboardId keyboardId = keyboard.mId;
- final int actionId = keyboardId.imeAction();
- final int resId;
-
- // Always use the label, if available.
- if (!TextUtils.isEmpty(key.getLabel())) {
- return key.getLabel().trim();
- }
-
- // Otherwise, use the action ID.
- switch (actionId) {
- case EditorInfo.IME_ACTION_SEARCH:
- resId = R.string.spoken_description_search;
- break;
- case EditorInfo.IME_ACTION_GO:
- resId = R.string.label_go_key;
- break;
- case EditorInfo.IME_ACTION_SEND:
- resId = R.string.label_send_key;
- break;
- case EditorInfo.IME_ACTION_NEXT:
- resId = R.string.label_next_key;
- break;
- case EditorInfo.IME_ACTION_DONE:
- resId = R.string.label_done_key;
- break;
- case EditorInfo.IME_ACTION_PREVIOUS:
- resId = R.string.label_previous_key;
- break;
- default:
- resId = R.string.spoken_description_return;
- }
- return context.getString(resId);
- }
-
- /**
- * 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.
- */
- public String getDescriptionForCodePoint(final Context context, final int codePoint) {
- // If the key description should be obscured, now is the time to do it.
- final int index = mKeyCodeMap.indexOfKey(codePoint);
- if (index >= 0) {
- return context.getString(mKeyCodeMap.valueAt(index));
- }
- final String accentedLetter = getSpokenAccentedLetterDescription(context, codePoint);
- if (accentedLetter != null) {
- return accentedLetter;
- }
- // Here, code
may be a base (non-accented) letter.
- final String unsupportedSymbol = getSpokenSymbolDescription(context, codePoint);
- if (unsupportedSymbol != null) {
- return unsupportedSymbol;
- }
- final String emojiDescription = getSpokenEmojiDescription(context, codePoint);
- if (emojiDescription != null) {
- return emojiDescription;
- }
- if (Character.isDefined(codePoint) && !Character.isISOControl(codePoint)) {
- return StringUtils.newSingleCodePointString(codePoint);
- }
- return null;
- }
-
- // TODO: Remove this method once TTS supports those accented letters' verbalization.
- private String getSpokenAccentedLetterDescription(final Context context, final int code) {
- final boolean isUpperCase = Character.isUpperCase(code);
- final int baseCode = isUpperCase ? Character.toLowerCase(code) : code;
- final int baseIndex = mKeyCodeMap.indexOfKey(baseCode);
- final int resId = (baseIndex >= 0) ? mKeyCodeMap.valueAt(baseIndex)
- : getSpokenDescriptionId(context, baseCode, SPOKEN_LETTER_RESOURCE_NAME_FORMAT);
- if (resId == 0) {
- return null;
- }
- final String spokenText = context.getString(resId);
- return isUpperCase ? context.getString(R.string.spoken_description_upper_case, spokenText)
- : spokenText;
- }
-
- // TODO: Remove this method once TTS supports those symbols' verbalization.
- private String getSpokenSymbolDescription(final Context context, final int code) {
- final int resId = getSpokenDescriptionId(context, code, SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT);
- if (resId == 0) {
- return null;
- }
- final String spokenText = context.getString(resId);
- if (!TextUtils.isEmpty(spokenText)) {
- return spokenText;
- }
- // If a translated description is empty, fall back to unknown symbol description.
- return context.getString(R.string.spoken_symbol_unknown);
- }
-
- // TODO: Remove this method once TTS supports emoji verbalization.
- private String getSpokenEmojiDescription(final Context context, final int code) {
- final int resId = getSpokenDescriptionId(context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT);
- if (resId == 0) {
- return null;
- }
- final String spokenText = context.getString(resId);
- if (!TextUtils.isEmpty(spokenText)) {
- return spokenText;
- }
- // If a translated description is empty, fall back to unknown emoji description.
- return context.getString(R.string.spoken_emoji_unknown);
- }
-
- private int getSpokenDescriptionId(final Context context, final int code,
- final String resourceNameFormat) {
- final String resourceName = String.format(Locale.ROOT, resourceNameFormat, code);
- final Resources resources = context.getResources();
- // Note that the resource package name may differ from the context package name.
- final String resourcePackageName = resources.getResourcePackageName(
- R.string.spoken_description_unknown);
- final int resId = resources.getIdentifier(resourceName, "string", resourcePackageName);
- if (resId != 0) {
- mKeyCodeMap.append(code, resId);
- }
- return resId;
- }
-
- // TODO: Remove this method once TTS supports emoticon verbalization.
- private static String getSpokenEmoticonDescription(final Context context,
- final String outputText) {
- final StringBuilder sb = new StringBuilder(SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX);
- final int textLength = outputText.length();
- for (int index = 0; index < textLength; index = outputText.offsetByCodePoints(index, 1)) {
- final int codePoint = outputText.codePointAt(index);
- sb.append(String.format(Locale.ROOT, SPOKEN_EMOTICON_CODE_POINT_FORMAT, codePoint));
- }
- final String resourceName = sb.toString();
- final Resources resources = context.getResources();
- // Note that the resource package name may differ from the context package name.
- final String resourcePackageName = resources.getResourcePackageName(
- R.string.spoken_description_unknown);
- final int resId = resources.getIdentifier(resourceName, "string", resourcePackageName);
- return (resId == 0) ? null : resources.getString(resId);
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/KeyCodeDescriptionMapper.kt b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/KeyCodeDescriptionMapper.kt
new file mode 100644
index 00000000..6d3f9074
--- /dev/null
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/KeyCodeDescriptionMapper.kt
@@ -0,0 +1,288 @@
+package org.dslul.openboard.inputmethod.accessibility
+
+import android.content.Context
+import android.text.TextUtils
+import android.util.Log
+import android.util.SparseIntArray
+import android.view.inputmethod.EditorInfo
+import org.dslul.openboard.inputmethod.keyboard.Key
+import org.dslul.openboard.inputmethod.keyboard.Keyboard
+import org.dslul.openboard.inputmethod.keyboard.KeyboardId
+import org.dslul.openboard.inputmethod.latin.R
+import org.dslul.openboard.inputmethod.latin.common.Constants
+import org.dslul.openboard.inputmethod.latin.common.StringUtils
+import java.util.*
+
+internal class KeyCodeDescriptionMapper private constructor() {
+ // Sparse array of spoken description resource IDs indexed by key codes
+ private val mKeyCodeMap = SparseIntArray()
+
+ /**
+ * 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
+ */
+ 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)
+ if (description != null) {
+ return description
+ }
+ }
+ 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.
+ return getDescriptionForActionKey(context, keyboard, key)
+ }
+ if (code == Constants.CODE_OUTPUT_TEXT) {
+ val outputText = key.outputText
+ 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.
+ 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.
+ */
+ 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))
+ }
+ val accentedLetter = getSpokenAccentedLetterDescription(context, codePoint)
+ if (accentedLetter != null) {
+ return accentedLetter
+ }
+ // Here, code
may be a base (non-accented) letter.
+ val unsupportedSymbol = getSpokenSymbolDescription(context, codePoint)
+ if (unsupportedSymbol != null) {
+ return unsupportedSymbol
+ }
+ val emojiDescription = getSpokenEmojiDescription(context, codePoint)
+ if (emojiDescription != null) {
+ return emojiDescription
+ }
+ return if (Character.isDefined(codePoint) && !Character.isISOControl(codePoint)) {
+ StringUtils.newSingleCodePointString(codePoint)
+ } else null
+ }
+
+ // TODO: Remove this method once TTS supports those accented letters' verbalization.
+ private fun getSpokenAccentedLetterDescription(context: Context, code: Int): String? {
+ val isUpperCase = Character.isUpperCase(code)
+ val baseCode = if (isUpperCase) Character.toLowerCase(code) else code
+ val baseIndex = mKeyCodeMap.indexOfKey(baseCode)
+ val resId = if (baseIndex >= 0) mKeyCodeMap.valueAt(baseIndex) else getSpokenDescriptionId(context, baseCode, SPOKEN_LETTER_RESOURCE_NAME_FORMAT)
+ if (resId == 0) {
+ return null
+ }
+ val spokenText = context.getString(resId)
+ return if (isUpperCase) context.getString(R.string.spoken_description_upper_case, spokenText) else spokenText
+ }
+
+ // TODO: Remove this method once TTS supports those symbols' verbalization.
+ private fun getSpokenSymbolDescription(context: Context, code: Int): String? {
+ val resId = getSpokenDescriptionId(context, code, SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT)
+ if (resId == 0) {
+ return null
+ }
+ val spokenText = context.getString(resId)
+ return if (!TextUtils.isEmpty(spokenText)) {
+ spokenText
+ } else context.getString(R.string.spoken_symbol_unknown)
+ // If a translated description is empty, fall back to unknown symbol description.
+ }
+
+ // TODO: Remove this method once TTS supports emoji verbalization.
+ private fun getSpokenEmojiDescription(context: Context, code: Int): String? {
+ val resId = getSpokenDescriptionId(context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT)
+ if (resId == 0) {
+ return null
+ }
+ val spokenText = context.getString(resId)
+ return if (!TextUtils.isEmpty(spokenText)) {
+ spokenText
+ } else context.getString(R.string.spoken_emoji_unknown)
+ // If a translated description is empty, fall back to unknown emoji description.
+ }
+
+ private fun getSpokenDescriptionId(context: Context, code: Int,
+ resourceNameFormat: String): Int {
+ 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 resId = resources.getIdentifier(resourceName, "string", resourcePackageName)
+ if (resId != 0) {
+ mKeyCodeMap.append(code, resId)
+ }
+ return resId
+ }
+
+ companion object {
+ private val TAG = KeyCodeDescriptionMapper::class.java.simpleName
+ private const val SPOKEN_LETTER_RESOURCE_NAME_FORMAT = "spoken_accented_letter_%04X"
+ private const val SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT = "spoken_symbol_%04X"
+ private const val SPOKEN_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X"
+ private const val SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX = "spoken_emoticon"
+ private const val SPOKEN_EMOTICON_CODE_POINT_FORMAT = "_%02X"
+ // The resource ID of the string spoken for obscured keys
+ private const val OBSCURED_KEY_RES_ID = R.string.spoken_description_dot
+ 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
+ */
+ private fun getDescriptionForSwitchAlphaSymbol(context: Context,
+ keyboard: Keyboard?): String? {
+ 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_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.
+ */
+ private fun getDescriptionForShiftKey(context: Context,
+ keyboard: Keyboard?): String {
+ 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_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.
+ */
+ private fun getDescriptionForActionKey(context: Context, keyboard: Keyboard?,
+ key: Key): String {
+ val keyboardId = keyboard!!.mId
+ val actionId = keyboardId.imeAction()
+ val resId: Int
+ // Always use the label, if available.
+ if (!TextUtils.isEmpty(key.label)) {
+ return key.label!!.trim { it <= ' ' }
+ }
+ resId = when (actionId) {
+ EditorInfo.IME_ACTION_SEARCH -> R.string.spoken_description_search
+ 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)
+ }
+
+ // TODO: Remove this method once TTS supports emoticon verbalization.
+ private fun getSpokenEmoticonDescription(context: Context,
+ outputText: String?): String? {
+ val sb = StringBuilder(SPOKEN_EMOTICON_RESOURCE_NAME_PREFIX)
+ val textLength = outputText!!.length
+ var index = 0
+ while (index < textLength) {
+ val codePoint = outputText.codePointAt(index)
+ sb.append(String.format(Locale.ROOT, SPOKEN_EMOTICON_CODE_POINT_FORMAT, codePoint))
+ index = outputText.offsetByCodePoints(index, 1)
+ }
+ 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 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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/KeyboardAccessibilityDelegate.java b/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
deleted file mode 100644
index 57b83154..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.dslul.openboard.inputmethod.accessibility;
-
-import android.content.Context;
-import android.os.SystemClock;
-import androidx.core.view.AccessibilityDelegateCompat;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityEvent;
-
-import org.dslul.openboard.inputmethod.keyboard.Key;
-import org.dslul.openboard.inputmethod.keyboard.KeyDetector;
-import org.dslul.openboard.inputmethod.keyboard.Keyboard;
-import org.dslul.openboard.inputmethod.keyboard.KeyboardView;
-
-/**
- * This class represents a delegate that can be registered in a class that extends
- * {@link KeyboardView} to enhance accessibility support via composition rather via inheritance.
- *
- * To implement accessibility mode, the target keyboard view has to:
- * - Call {@link #setKeyboard(Keyboard)} when a new keyboard is set to the keyboard view.
- * - Dispatch a hover event by calling {@link #onHoverEnter(MotionEvent)}.
- *
- * @param
- * Note: This method will be called even if accessibility is not
- * enabled.
- * @param keyboard The keyboard that is being set to the wrapping view.
- */
- public void setKeyboard(final Keyboard keyboard) {
- if (keyboard == null) {
- return;
- }
- if (mAccessibilityNodeProvider != null) {
- mAccessibilityNodeProvider.setKeyboard(keyboard);
- }
- mKeyboard = keyboard;
- }
-
- protected final Keyboard getKeyboard() {
- return mKeyboard;
- }
-
- protected final void setLastHoverKey(final Key key) {
- mLastHoverKey = key;
- }
-
- protected final Key getLastHoverKey() {
- return mLastHoverKey;
- }
-
- /**
- * Sends a window state change event with the specified string resource id.
- *
- * @param resId The string resource id of the text to send with the event.
- */
- protected void sendWindowStateChanged(final int resId) {
- if (resId == 0) {
- return;
- }
- final Context context = mKeyboardView.getContext();
- sendWindowStateChanged(context.getString(resId));
- }
-
- /**
- * Sends a window state change event with the specified text.
- *
- * @param text The text to send with the event.
- */
- protected void sendWindowStateChanged(final String text) {
- final AccessibilityEvent stateChange = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- mKeyboardView.onInitializeAccessibilityEvent(stateChange);
- stateChange.getText().add(text);
- stateChange.setContentDescription(null);
-
- final ViewParent parent = mKeyboardView.getParent();
- if (parent != null) {
- parent.requestSendAccessibilityEvent(mKeyboardView, stateChange);
- }
- }
-
- /**
- * Delegate method for View.getAccessibilityNodeProvider(). This method is called in SDK
- * version 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) and higher to obtain the virtual
- * node hierarchy provider.
- *
- * @param host The host view for the provider.
- * @return The accessibility node provider for the current keyboard.
- */
- @Override
- public KeyboardAccessibilityNodeProvider
- * A virtual sub-tree is composed of imaginary {@link View}s that are reported
- * as a part of the view hierarchy for accessibility purposes. This enables
- * custom views that draw complex content to report them selves as a tree of
- * virtual views, thus conveying their logical structure.
- *
- * A virtual descendant is an imaginary View that is reported as a part of
- * the view hierarchy for accessibility purposes. This enables custom views
- * that draw complex content to report them selves as a tree of virtual
- * views, thus conveying their logical structure.
- *
- * The implementer is responsible for obtaining an accessibility node info
- * from the pool of reusable instances and setting the desired properties of
- * the node info before returning it.
- * event
is on.
- */
- protected final Key getHoverKeyOf(final MotionEvent event) {
- final int actionIndex = event.getActionIndex();
- final int x = (int)event.getX(actionIndex);
- final int y = (int)event.getY(actionIndex);
- return mKeyDetector.detectHitKey(x, y);
- }
-
- /**
- * Receives hover events when touch exploration is turned on in SDK versions ICS and higher.
- *
- * @param event The hover event.
- * @return {@code true} if the event is handled.
- */
- public boolean onHoverEvent(final MotionEvent event) {
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- onHoverEnter(event);
- break;
- case MotionEvent.ACTION_HOVER_MOVE:
- onHoverMove(event);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- onHoverExit(event);
- break;
- default:
- Log.w(getClass().getSimpleName(), "Unknown hover event: " + event);
- break;
- }
- return true;
- }
-
- /**
- * Process {@link MotionEvent#ACTION_HOVER_ENTER} event.
- *
- * @param event A hover enter event.
- */
- protected void onHoverEnter(final MotionEvent event) {
- final Key key = getHoverKeyOf(event);
- if (DEBUG_HOVER) {
- Log.d(TAG, "onHoverEnter: key=" + key);
- }
- if (key != null) {
- onHoverEnterTo(key);
- }
- setLastHoverKey(key);
- }
-
- /**
- * Process {@link MotionEvent#ACTION_HOVER_MOVE} event.
- *
- * @param event A hover move event.
- */
- protected void onHoverMove(final MotionEvent event) {
- final Key lastKey = getLastHoverKey();
- final Key key = getHoverKeyOf(event);
- if (key != lastKey) {
- if (lastKey != null) {
- onHoverExitFrom(lastKey);
- }
- if (key != null) {
- onHoverEnterTo(key);
- }
- }
- if (key != null) {
- onHoverMoveWithin(key);
- }
- setLastHoverKey(key);
- }
-
- /**
- * Process {@link MotionEvent#ACTION_HOVER_EXIT} event.
- *
- * @param event A hover exit event.
- */
- protected void onHoverExit(final MotionEvent event) {
- final Key lastKey = getLastHoverKey();
- if (DEBUG_HOVER) {
- Log.d(TAG, "onHoverExit: key=" + getHoverKeyOf(event) + " last=" + lastKey);
- }
- if (lastKey != null) {
- onHoverExitFrom(lastKey);
- }
- final Key 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.
- if (key != null) {
- performClickOn(key);
- onHoverExitFrom(key);
- }
- setLastHoverKey(null);
- }
-
- /**
- * Perform click on a key.
- *
- * @param key A key to be registered.
- */
- public void performClickOn(final Key key) {
- if (DEBUG_HOVER) {
- Log.d(TAG, "performClickOn: key=" + key);
- }
- simulateTouchEvent(MotionEvent.ACTION_DOWN, key);
- simulateTouchEvent(MotionEvent.ACTION_UP, key);
- }
-
- /**
- * Simulating a touch event by injecting a synthesized touch event into {@link KeyboardView}.
- *
- * @param touchAction The action of the synthesizing touch event.
- * @param key The key that a synthesized touch event is on.
- */
- private void simulateTouchEvent(final int touchAction, final Key key) {
- final int x = key.getHitBox().centerX();
- final int y = key.getHitBox().centerY();
- final long eventTime = SystemClock.uptimeMillis();
- final MotionEvent touchEvent = MotionEvent.obtain(
- eventTime, eventTime, touchAction, x, y, 0 /* metaState */);
- mKeyboardView.onTouchEvent(touchEvent);
- touchEvent.recycle();
- }
-
- /**
- * Handles a hover enter event on a key.
- *
- * @param key The currently hovered key.
- */
- protected void onHoverEnterTo(final Key key) {
- if (DEBUG_HOVER) {
- Log.d(TAG, "onHoverEnterTo: key=" + key);
- }
- key.onPressed();
- mKeyboardView.invalidateKey(key);
- final KeyboardAccessibilityNodeProvidervirtualViewId
or
- * the host View itself if virtualViewId
equals to {@link View#NO_ID}.
- * wordView
should be enabled even
// when it is empty to avoid announcing as "disabled".
wordView.setEnabled(!TextUtils.isEmpty(word)
- || AccessibilityUtils.getInstance().isTouchExplorationEnabled());
+ || AccessibilityUtils.Companion.getInstance().isTouchExplorationEnabled());
return wordView;
}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java
index be1b8267..905bffed 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -376,7 +376,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
// upward. Further {@link MotionEvent}s will be delivered to
// {@link #onTouchEvent(MotionEvent)}.
mNeedsToTransformTouchEventToHoverEvent =
- AccessibilityUtils.getInstance().isTouchExplorationEnabled();
+ AccessibilityUtils.Companion.getInstance().isTouchExplorationEnabled();
mIsDispatchingHoverEventToMoreSuggestions = false;
return true;
}