Skip to content

Commit a9a9035

Browse files
authored
Merge pull request #188 from Merkost/talkback_improvements
Talkback improvements
2 parents 7ac8bc9 + 9519caa commit a9a9035

File tree

48 files changed

+196
-103
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+196
-103
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.simplemobiletools.keyboard.helpers
2+
3+
import android.graphics.Rect
4+
import android.os.Bundle
5+
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
6+
import androidx.customview.widget.ExploreByTouchHelper
7+
import com.simplemobiletools.keyboard.views.MyKeyboardView
8+
9+
class AccessHelper(
10+
private val keyboardView: MyKeyboardView,
11+
private val keys: List<MyKeyboard.Key>
12+
) : ExploreByTouchHelper(keyboardView) {
13+
14+
/**
15+
* We need to populate the list with the IDs of all of the visible virtual views (the intervals in the chart).
16+
* In our case, all keys are always visible, so we’ll return a list of all IDs.
17+
*/
18+
override fun getVisibleVirtualViews(virtualViewIds: MutableList<Int>) {
19+
val keysSize = keys.size
20+
for (i in 0 until keysSize) {
21+
virtualViewIds.add(i)
22+
}
23+
}
24+
25+
/**
26+
* For this function, we need to return the ID of the virtual view that’s under the x, y position,
27+
* or ExploreByTouchHelper.HOST_ID if there’s no item at those coordinates.
28+
*/
29+
override fun getVirtualViewAt(x: Float, y: Float): Int {
30+
val rects = keys.map {
31+
Rect(it.x, it.y, it.x + it.width, it.y + it.height)
32+
}
33+
34+
return rects.firstOrNull { it.contains(x.toInt(), y.toInt()) }?.let { exactRect ->
35+
rects.indexOf(exactRect)
36+
} ?: HOST_ID
37+
}
38+
39+
/**
40+
* This is where we provide all the metadata for our virtual view.
41+
* We need to set the content description (or text, if it’s presented visually) and set the bounds in parent.
42+
*/
43+
override fun onPopulateNodeForVirtualView(virtualViewId: Int, node: AccessibilityNodeInfoCompat) {
44+
node.className = keyboardView::class.simpleName
45+
val key = keys.getOrNull(virtualViewId)
46+
node.contentDescription = key?.getContentDescription(keyboardView.context) ?: ""
47+
val bounds = updateBoundsForInterval(virtualViewId)
48+
node.setBoundsInParent(bounds)
49+
}
50+
51+
/**
52+
* We need to set the content description (or text, if it’s presented visually) and set the bounds in parent.
53+
* The bounds in the parent should match the logic in the onDraw() function.
54+
*/
55+
private fun updateBoundsForInterval(index: Int): Rect {
56+
val keys = keys
57+
val key = keys.getOrNull(index) ?: return Rect()
58+
return Rect().apply {
59+
left = key.x
60+
top = key.y
61+
right = key.x + key.width
62+
bottom = key.y + key.height
63+
}
64+
}
65+
66+
override fun onPerformActionForVirtualView(virtualViewId: Int, action: Int, arguments: Bundle?): Boolean {
67+
return false
68+
}
69+
}

app/src/main/kotlin/com/simplemobiletools/keyboard/helpers/MyKeyboard.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class MyKeyboard {
4343
var mMinWidth = 0
4444

4545
/** List of keys in this keyboard */
46-
var mKeys: MutableList<Key?>? = null
46+
var mKeys: MutableList<Key>? = null
4747

4848
/** Width of the screen available to fit the keyboard */
4949
private var mDisplayWidth = 0
@@ -225,6 +225,21 @@ class MyKeyboard {
225225
a.recycle()
226226
}
227227

228+
/**
229+
* Content description for talkback functional
230+
*/
231+
fun getContentDescription(context: Context): CharSequence {
232+
return when (code) {
233+
KEYCODE_SHIFT -> context.getString(R.string.keycode_shift)
234+
KEYCODE_MODE_CHANGE -> context.getString(R.string.keycode_mode_change)
235+
KEYCODE_ENTER -> context.getString(R.string.keycode_enter)
236+
KEYCODE_DELETE -> context.getString(R.string.keycode_delete)
237+
KEYCODE_SPACE -> context.getString(R.string.keycode_space)
238+
KEYCODE_EMOJI -> context.getString(R.string.emojis)
239+
else -> label
240+
}
241+
}
242+
228243
/** Create an empty key with no attributes. */
229244
init {
230245
height = parent.defaultHeight
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.simplemobiletools.keyboard.interfaces
2+
3+
interface OnKeyboardActionListener {
4+
/**
5+
* Called when the user presses a key. This is sent before the [.onKey] is called. For keys that repeat, this is only called once.
6+
* @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key, the value will be zero.
7+
*/
8+
fun onPress(primaryCode: Int)
9+
10+
/**
11+
* Send a key press to the listener.
12+
* @param code this is the key that was pressed
13+
*/
14+
fun onKey(code: Int)
15+
16+
/**
17+
* Called when the finger has been lifted after pressing a key
18+
*/
19+
fun onActionUp()
20+
21+
/**
22+
* Called when the user long presses Space and moves to the left
23+
*/
24+
fun moveCursorLeft()
25+
26+
/**
27+
* Called when the user long presses Space and moves to the right
28+
*/
29+
fun moveCursorRight()
30+
31+
/**
32+
* Sends a sequence of characters to the listener.
33+
* @param text the string to be displayed.
34+
*/
35+
fun onText(text: String)
36+
37+
/**
38+
* Called to force the KeyboardView to reload the keyboard
39+
*/
40+
fun reloadKeyboard()
41+
}

app/src/main/kotlin/com/simplemobiletools/keyboard/services/SimpleKeyboardIME.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ import com.simplemobiletools.commons.extensions.getSharedPrefs
1515
import com.simplemobiletools.keyboard.R
1616
import com.simplemobiletools.keyboard.extensions.config
1717
import com.simplemobiletools.keyboard.helpers.*
18+
import com.simplemobiletools.keyboard.interfaces.OnKeyboardActionListener
1819
import com.simplemobiletools.keyboard.views.MyKeyboardView
1920
import kotlinx.android.synthetic.main.keyboard_view_keyboard.view.*
2021

2122
// based on https://www.androidauthority.com/lets-build-custom-keyboard-android-832362/
22-
class SimpleKeyboardIME : InputMethodService(), MyKeyboardView.OnKeyboardActionListener, SharedPreferences.OnSharedPreferenceChangeListener {
23+
class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, SharedPreferences.OnSharedPreferenceChangeListener {
2324
private var SHIFT_PERM_TOGGLE_SPEED = 500 // how quickly do we have to doubletap shift to enable permanent caps lock
2425
private val KEYBOARD_LETTERS = 0
2526
private val KEYBOARD_SYMBOLS = 1

app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt

Lines changed: 20 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ import android.os.Message
1616
import android.util.AttributeSet
1717
import android.util.TypedValue
1818
import android.view.*
19-
import android.view.accessibility.AccessibilityEvent
20-
import android.view.accessibility.AccessibilityManager
2119
import android.view.animation.AccelerateInterpolator
2220
import android.view.inputmethod.EditorInfo
2321
import android.widget.PopupWindow
2422
import android.widget.TextView
2523
import androidx.core.animation.doOnEnd
2624
import androidx.core.animation.doOnStart
25+
import androidx.core.view.ViewCompat
2726
import androidx.emoji2.text.EmojiCompat
2827
import androidx.emoji2.text.EmojiCompat.EMOJI_SUPPORTED
2928
import com.simplemobiletools.commons.extensions.*
@@ -43,6 +42,7 @@ import com.simplemobiletools.keyboard.helpers.MyKeyboard.Companion.KEYCODE_ENTER
4342
import com.simplemobiletools.keyboard.helpers.MyKeyboard.Companion.KEYCODE_MODE_CHANGE
4443
import com.simplemobiletools.keyboard.helpers.MyKeyboard.Companion.KEYCODE_SHIFT
4544
import com.simplemobiletools.keyboard.helpers.MyKeyboard.Companion.KEYCODE_SPACE
45+
import com.simplemobiletools.keyboard.interfaces.OnKeyboardActionListener
4646
import com.simplemobiletools.keyboard.interfaces.RefreshClipsListener
4747
import com.simplemobiletools.keyboard.models.Clip
4848
import com.simplemobiletools.keyboard.models.ClipsSectionLabel
@@ -52,49 +52,18 @@ import kotlinx.android.synthetic.main.keyboard_view_keyboard.view.*
5252
import java.util.*
5353

5454
@SuppressLint("UseCompatLoadingForDrawables", "ClickableViewAccessibility")
55-
class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int = 0) :
56-
View(context, attrs, defStyleRes) {
57-
58-
interface OnKeyboardActionListener {
59-
/**
60-
* Called when the user presses a key. This is sent before the [.onKey] is called. For keys that repeat, this is only called once.
61-
* @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key, the value will be zero.
62-
*/
63-
fun onPress(primaryCode: Int)
64-
65-
/**
66-
* Send a key press to the listener.
67-
* @param code this is the key that was pressed
68-
*/
69-
fun onKey(code: Int)
70-
71-
/**
72-
* Called when the finger has been lifted after pressing a key
73-
*/
74-
fun onActionUp()
75-
76-
/**
77-
* Called when the user long presses Space and moves to the left
78-
*/
79-
fun moveCursorLeft()
80-
81-
/**
82-
* Called when the user long presses Space and moves to the right
83-
*/
84-
fun moveCursorRight()
85-
86-
/**
87-
* Sends a sequence of characters to the listener.
88-
* @param text the string to be displayed.
89-
*/
90-
fun onText(text: String)
91-
92-
/**
93-
* Called to force the KeyboardView to reload the keyboard
94-
*/
95-
fun reloadKeyboard()
55+
class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int = 0) : View(context, attrs, defStyleRes) {
56+
57+
override fun dispatchHoverEvent(event: MotionEvent): Boolean {
58+
return if (accessHelper?.dispatchHoverEvent(event) == true) {
59+
true
60+
} else {
61+
super.dispatchHoverEvent(event)
62+
}
9663
}
9764

65+
private var accessHelper: AccessHelper? = null
66+
9867
private var mKeyboard: MyKeyboard? = null
9968
private var mCurrentKeyIndex: Int = NOT_A_KEY
10069

@@ -184,9 +153,6 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
184153
/** The canvas for the above mutable keyboard bitmap */
185154
private var mCanvas: Canvas? = null
186155

187-
/** The accessibility manager for accessibility support */
188-
private val mAccessibilityManager: AccessibilityManager
189-
190156
private var mHandler: Handler? = null
191157

192158
companion object {
@@ -245,7 +211,6 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
245211
mPaint.textAlign = Align.CENTER
246212
mPaint.alpha = 255
247213
mMiniKeyboardCache = HashMap()
248-
mAccessibilityManager = (context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager)
249214
mPopupMaxMoveDistance = resources.getDimension(R.dimen.popup_max_move_distance)
250215
mTopSmallNumberSize = resources.getDimension(R.dimen.small_text_size)
251216
mTopSmallNumberMarginWidth = resources.getDimension(R.dimen.top_small_number_margin_width)
@@ -301,6 +266,10 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
301266
invalidateAllKeys()
302267
computeProximityThreshold(keyboard)
303268
mMiniKeyboardCache.clear()
269+
270+
accessHelper = AccessHelper(this, mKeyboard?.mKeys.orEmpty())
271+
ViewCompat.setAccessibilityDelegate(this, accessHelper)
272+
304273
// Not really necessary to do every time, but will free up views
305274
// Switching to a different keyboard should abort any pending keys so that the key up
306275
// doesn't get delivered to the old or new keyboard
@@ -479,7 +448,7 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
479448

480449
private fun adjustCase(label: CharSequence): CharSequence? {
481450
var newLabel: CharSequence? = label
482-
if (newLabel != null && newLabel.isNotEmpty() && mKeyboard!!.mShiftState != ShiftState.OFF && newLabel.length < 3 && Character.isLowerCase(newLabel[0])) {
451+
if (!newLabel.isNullOrEmpty() && mKeyboard!!.mShiftState != ShiftState.OFF && newLabel.length < 3 && Character.isLowerCase(newLabel[0])) {
483452
newLabel = newLabel.toString().uppercase(Locale.getDefault())
484453
}
485454
return newLabel
@@ -644,10 +613,7 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
644613
val secondaryIconBottom = secondaryIconTop + secondaryIconHeight
645614

646615
secondaryIcon.setBounds(
647-
secondaryIconLeft,
648-
secondaryIconTop,
649-
secondaryIconRight,
650-
secondaryIconBottom
616+
secondaryIconLeft, secondaryIconTop, secondaryIconRight, secondaryIconBottom
651617
)
652618
secondaryIcon.draw(canvas)
653619

@@ -833,29 +799,6 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
833799
val oldKeyIndex = mCurrentKeyIndex
834800
val previewPopup = mPreviewPopup
835801
mCurrentKeyIndex = keyIndex
836-
// Release the old key and press the new key
837-
val keys = mKeys
838-
if (oldKeyIndex != mCurrentKeyIndex) {
839-
if (oldKeyIndex != NOT_A_KEY && keys.size > oldKeyIndex) {
840-
val oldKey = keys[oldKeyIndex]
841-
oldKey.pressed = false
842-
invalidateKey(oldKeyIndex)
843-
val keyCode = oldKey.code
844-
sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode)
845-
}
846-
847-
if (mCurrentKeyIndex != NOT_A_KEY && keys.size > mCurrentKeyIndex) {
848-
val newKey = keys[mCurrentKeyIndex]
849-
850-
val code = newKey.code
851-
if (context.config.showKeyBorders || (code == KEYCODE_SHIFT || code == KEYCODE_MODE_CHANGE || code == KEYCODE_DELETE || code == KEYCODE_ENTER || code == KEYCODE_SPACE)) {
852-
newKey.pressed = true
853-
}
854-
855-
invalidateKey(mCurrentKeyIndex)
856-
sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, code)
857-
}
858-
}
859802

860803
if (!context.config.showPopupOnKeypress) {
861804
return
@@ -866,8 +809,7 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
866809
if (previewPopup.isShowing) {
867810
if (keyIndex == NOT_A_KEY) {
868811
mHandler!!.sendMessageDelayed(
869-
mHandler!!.obtainMessage(MSG_REMOVE_PREVIEW),
870-
DELAY_AFTER_PREVIEW.toLong()
812+
mHandler!!.obtainMessage(MSG_REMOVE_PREVIEW), DELAY_AFTER_PREVIEW.toLong()
871813
)
872814
}
873815
}
@@ -960,22 +902,6 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
960902
}
961903
}
962904

963-
private fun sendAccessibilityEventForUnicodeCharacter(eventType: Int, code: Int) {
964-
if (mAccessibilityManager.isEnabled) {
965-
val event = AccessibilityEvent.obtain(eventType)
966-
onInitializeAccessibilityEvent(event)
967-
val text: String = when (code) {
968-
KEYCODE_DELETE -> context.getString(R.string.keycode_delete)
969-
KEYCODE_ENTER -> context.getString(R.string.keycode_enter)
970-
KEYCODE_MODE_CHANGE -> context.getString(R.string.keycode_mode_change)
971-
KEYCODE_SHIFT -> context.getString(R.string.keycode_shift)
972-
else -> code.toChar().toString()
973-
}
974-
event.text.add(text)
975-
mAccessibilityManager.sendAccessibilityEvent(event)
976-
}
977-
}
978-
979905
/**
980906
* Requests a redraw of the entire keyboard. Calling [.invalidate] is not sufficient because the keyboard renders the keys to an off-screen buffer and
981907
* an invalidate() only draws the cached buffer.
@@ -1090,8 +1016,7 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
10901016
mMiniKeyboard!!.setKeyboard(keyboard)
10911017
mPopupParent = this
10921018
mMiniKeyboardContainer!!.measure(
1093-
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
1094-
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)
1019+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)
10951020
)
10961021
mMiniKeyboardCache[popupKey] = mMiniKeyboardContainer
10971022
} else {

app/src/main/res/layout/keyboard_view_keyboard.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
android:layout_height="@dimen/toolbar_icon_height"
6464
android:layout_marginEnd="@dimen/medium_margin"
6565
android:background="?android:attr/selectableItemBackgroundBorderless"
66-
android:contentDescription="@string/settings"
66+
android:contentDescription="@string/clipboard_pinned"
6767
android:padding="@dimen/small_margin"
6868
android:src="@drawable/ic_clipboard_vector"
6969
app:layout_constraintBottom_toBottomOf="parent"

app/src/main/res/values-ar/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<string name="keycode_mode_change">تغيير نوع لوحة المفاتيح</string>
2727
<string name="keycode_shift">Shift</string>
2828
<string name="keycode_enter">Enter</string>
29+
<string name="keycode_space">Spacebar</string>
2930
<!-- Settings -->
3031
<string name="show_clipboard_content">إظهار محتوى الحافظة إذا كان متوفرا</string>
3132
<string name="show_popup">إظهار نافذة منبثقة عند الضغط على المفاتيح</string>

app/src/main/res/values-be/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<string name="keycode_mode_change">Змяніць тып клавіятуры</string>
2727
<string name="keycode_shift">Зрух</string>
2828
<string name="keycode_enter">Увайдзіце</string>
29+
<string name="keycode_space">Spacebar</string>
2930
<!-- Settings -->
3031
<string name="show_clipboard_content">Паказаць змесціва буфера абмену, калі яно даступна</string>
3132
<string name="show_popup">Паказваць усплывальнае акно пры націску клавішы</string>

app/src/main/res/values-bg/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<string name="keycode_mode_change">Промяна на типа клавиатура</string>
2727
<string name="keycode_shift">Shift</string>
2828
<string name="keycode_enter">Enter</string>
29+
<string name="keycode_space">Spacebar</string>
2930
<!-- Settings -->
3031
<string name="show_clipboard_content">Показване на клипборд съдържанието, ако е налично</string>
3132
<string name="show_popup">Показване на изскачащ прозорец при натискане на клавиш</string>

0 commit comments

Comments
 (0)