Support long-click for morekeys with Talkback

This commit is contained in:
Aleksandras Kostarevas 2024-09-23 20:11:15 +03:00
parent db758a975d
commit 2c632bd4bd
6 changed files with 71 additions and 65 deletions

View File

@ -29,10 +29,8 @@ import android.view.inputmethod.EditorInfo;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeProviderCompat;
import androidx.customview.widget.ExploreByTouchHelper; import androidx.customview.widget.ExploreByTouchHelper;
import org.futo.inputmethod.keyboard.Key; import org.futo.inputmethod.keyboard.Key;
@ -121,7 +119,7 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
*/ */
protected void sendWindowStateChanged(final String text) { protected void sendWindowStateChanged(final String text) {
final AccessibilityEvent stateChange = AccessibilityEvent.obtain( final AccessibilityEvent stateChange = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); AccessibilityEvent.TYPE_ANNOUNCEMENT);
mKeyboardView.onInitializeAccessibilityEvent(stateChange); mKeyboardView.onInitializeAccessibilityEvent(stateChange);
stateChange.getText().add(text); stateChange.getText().add(text);
stateChange.setContentDescription(null); stateChange.setContentDescription(null);
@ -183,10 +181,14 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
node.setFocusable(true); node.setFocusable(true);
node.setScreenReaderFocusable(true); node.setScreenReaderFocusable(true);
if(k.isActionKey() || k.getCode() == Constants.CODE_SWITCH_ALPHA_SYMBOL || k.getCode() == Constants.CODE_EMOJI || k.getCode() == Constants.CODE_SYMBOL_SHIFT) { if(k.isActionKey() || k.getCode() == Constants.CODE_SWITCH_ALPHA_SYMBOL || k.getCode() == Constants.CODE_EMOJI || k.getCode() == Constants.CODE_SYMBOL_SHIFT || (k.getCode() >= Constants.CODE_ACTION_0 && k.getCode() <= Constants.CODE_ACTION_MAX)) {
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
node.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
node.setClickable(true); node.setClickable(true);
} else { } else {
if(k.isLongPressEnabled()) {
node.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
}
node.setTextEntryKey(true); node.setTextEntryKey(true);
} }
} }
@ -200,6 +202,10 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
// Handle the click action for the virtual button // Handle the click action for the virtual button
performClickOn(k); performClickOn(k);
return true; return true;
} else if(action == AccessibilityNodeInfoCompat.ACTION_LONG_CLICK) {
// Show morekeys
performLongClickOn(k);
return true;
} }
return false; return false;
} }

View File

@ -227,38 +227,6 @@ public final class MainKeyboardAccessibilityDelegate
Log.d(TAG, "performLongClickOn: key=" + key); Log.d(TAG, "performLongClickOn: key=" + key);
} }
final PointerTracker tracker = PointerTracker.getPointerTracker(HOVER_EVENT_POINTER_ID); final PointerTracker tracker = PointerTracker.getPointerTracker(HOVER_EVENT_POINTER_ID);
final long eventTime = SystemClock.uptimeMillis(); mKeyboardView.showMoreKeysKeyboard(key, tracker);
final int x = key.getHitBox().centerX();
final int y = key.getHitBox().centerY();
final MotionEvent downEvent = MotionEvent.obtain(
eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */);
// 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.
mBoundsToIgnoreHoverEvent.setEmpty();
return;
}
// This long press has handled at {@link MainKeyboardView#onLongPress(PointerTracker)}.
// We should ignore further hover events on this key.
mBoundsToIgnoreHoverEvent.set(key.getHitBox());
if (key.getHasNoPanelAutoMoreKey()) {
// This long press has registered a code point without showing a more keys keyboard.
// We should talk back the code point if possible.
final int codePointOfNoPanelAutoMoreKey = key.getMoreKeys().get(0).mCode;
final String text = KeyCodeDescriptionMapper.getInstance().getDescriptionForCodePoint(
mKeyboardView.getContext(), codePointOfNoPanelAutoMoreKey);
if (text != null) {
sendWindowStateChanged(text);
}
}
} }
} }

View File

@ -17,8 +17,8 @@
package org.futo.inputmethod.accessibility; package org.futo.inputmethod.accessibility;
import android.graphics.Rect; import android.graphics.Rect;
import android.util.Log; import android.view.HapticFeedbackConstants;
import android.view.MotionEvent; import android.view.SoundEffectConstants;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import androidx.core.view.accessibility.AccessibilityEventCompat; import androidx.core.view.accessibility.AccessibilityEventCompat;
@ -27,7 +27,6 @@ import androidx.core.view.accessibility.AccessibilityRecordCompat;
import org.futo.inputmethod.keyboard.Key; import org.futo.inputmethod.keyboard.Key;
import org.futo.inputmethod.keyboard.KeyDetector; import org.futo.inputmethod.keyboard.KeyDetector;
import org.futo.inputmethod.keyboard.MoreKeysKeyboardView; import org.futo.inputmethod.keyboard.MoreKeysKeyboardView;
import org.futo.inputmethod.keyboard.PointerTracker;
/** /**
* This class represents a delegate that can be registered in {@link MoreKeysKeyboardView} to * This class represents a delegate that can be registered in {@link MoreKeysKeyboardView} to
@ -57,6 +56,9 @@ public class MoreKeysKeyboardAccessibilityDelegate
public void onShowMoreKeysKeyboard() { public void onShowMoreKeysKeyboard() {
sendWindowStateChanged(mOpenAnnounceResId); sendWindowStateChanged(mOpenAnnounceResId);
mKeyboardView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
mKeyboardView.playSoundEffect(SoundEffectConstants.CLICK);
} }
public void onDismissMoreKeysKeyboard() { public void onDismissMoreKeysKeyboard() {

View File

@ -300,22 +300,6 @@ data class Key(
((actionFlags and KeyConsts.ACTION_FLAGS_ENABLE_LONG_PRESS) != 0 ((actionFlags and KeyConsts.ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
&& (labelFlags and KeyConsts.LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0) && (labelFlags and KeyConsts.LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0)
fun markAsLeftEdge(params: KeyboardParams) {
hitBox.left = params.mLeftPadding
}
fun markAsRightEdge(params: KeyboardParams) {
hitBox.right = params.mOccupiedWidth - params.mRightPadding
}
fun markAsTopEdge(params: KeyboardParams) {
hitBox.top = params.mTopPadding
}
fun markAsBottomEdge(params: KeyboardParams) {
hitBox.bottom = params.mOccupiedHeight + params.mBottomPadding
}
fun selectTypeface(params: KeyDrawParams): Typeface { fun selectTypeface(params: KeyDrawParams): Typeface {
return when (labelFlags and KeyConsts.LABEL_FLAGS_FONT_MASK) { return when (labelFlags and KeyConsts.LABEL_FLAGS_FONT_MASK) {
KeyConsts.LABEL_FLAGS_FONT_NORMAL -> Typeface.DEFAULT KeyConsts.LABEL_FLAGS_FONT_NORMAL -> Typeface.DEFAULT

View File

@ -248,10 +248,10 @@ public final class MoreKeysKeyboard extends Keyboard {
} }
public void markAsEdgeKey(final Key key, final int row) { public void markAsEdgeKey(final Key key, final int row) {
if (row == 0) //if (row == 0)
key.markAsTopEdge(this); // key.markAsTopEdge(this);
if (isTopRow(row)) //if (isTopRow(row))
key.markAsBottomEdge(this); // key.markAsBottomEdge(this);
} }
private boolean isTopRow(final int rowCount) { private boolean isTopRow(final int rowCount) {

View File

@ -25,9 +25,12 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull;
import org.futo.inputmethod.accessibility.AccessibilityUtils; import org.futo.inputmethod.accessibility.AccessibilityUtils;
import org.futo.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate; import org.futo.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate;
import org.futo.inputmethod.keyboard.internal.KeyDrawParams; import org.futo.inputmethod.keyboard.internal.KeyDrawParams;
@ -43,7 +46,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
private final int[] mCoordinates = CoordinateUtils.newInstance(); private final int[] mCoordinates = CoordinateUtils.newInstance();
private final Drawable mDivider; private final Drawable mDivider;
protected final KeyDetector mKeyDetector; protected final MoreKeysDetector mKeyDetector;
private Controller mController = EMPTY_CONTROLLER; private Controller mController = EMPTY_CONTROLLER;
protected KeyboardActionListener mListener; protected KeyboardActionListener mListener;
private int mOriginX; private int mOriginX;
@ -104,8 +107,6 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
@Override @Override
public void setKeyboard(final Keyboard keyboard) { public void setKeyboard(final Keyboard keyboard) {
super.setKeyboard(keyboard); super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
if (mAccessibilityDelegate == null) { if (mAccessibilityDelegate == null) {
mAccessibilityDelegate = new MoreKeysKeyboardAccessibilityDelegate( mAccessibilityDelegate = new MoreKeysKeyboardAccessibilityDelegate(
@ -114,8 +115,15 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_keys_keyboard); mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_keys_keyboard);
} }
mAccessibilityDelegate.setKeyboard(keyboard); mAccessibilityDelegate.setKeyboard(keyboard);
// No vertical correction
mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop());
} else { } else {
mAccessibilityDelegate = null; mAccessibilityDelegate = null;
// With vertical correction
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
} }
} }
@ -139,8 +147,13 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
container.setX(panelX); container.setX(panelX);
container.setY(panelY); container.setY(panelY);
if(AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
mOriginX = (int)container.getX() - CoordinateUtils.x(mCoordinates);
mOriginY = (int)container.getY() - CoordinateUtils.y(mCoordinates);
} else {
mOriginX = CoordinateUtils.x(touchOrigin) - getDefaultCoordX(); mOriginX = CoordinateUtils.x(touchOrigin) - getDefaultCoordX();
mOriginY = CoordinateUtils.y(touchOrigin) - container.getMeasuredHeight(); mOriginY = CoordinateUtils.y(touchOrigin) - container.getMeasuredHeight();
}
controller.onShowMoreKeysPanel(this); controller.onShowMoreKeysPanel(this);
final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
if (accessibilityDelegate != null if (accessibilityDelegate != null
@ -194,6 +207,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
* Performs the specific action for this panel when the user presses a key on the panel. * Performs the specific action for this panel when the user presses a key on the panel.
*/ */
protected void onKeyInput(final Key key, final int x, final int y) { protected void onKeyInput(final Key key, final int x, final int y) {
dismissMoreKeysPanel();
final int code = key.getCode(); final int code = key.getCode();
if (code == Constants.CODE_OUTPUT_TEXT) { if (code == Constants.CODE_OUTPUT_TEXT) {
mListener.onTextInput(mCurrentKey.getOutputText()); mListener.onTextInput(mCurrentKey.getOutputText());
@ -315,6 +330,36 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
public void showInParent(final ViewGroup parentView) { public void showInParent(final ViewGroup parentView) {
removeFromParent(); removeFromParent();
parentView.addView(getContainerView()); parentView.addView(getContainerView());
if(mAccessibilityDelegate != null && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
parentView.setTouchDelegate(new MyTouchDelegate(parentView, getContainerView()));
}
}
class MyTouchDelegate extends TouchDelegate {
public MyTouchDelegate(View parentView, View delegateView) {
super(new Rect(Integer.MIN_VALUE / 2, Integer.MIN_VALUE / 2, Integer.MAX_VALUE / 2 - 1, Integer.MAX_VALUE / 2 - 1), delegateView);
}
@Override
public boolean onTouchExplorationHoverEvent(@NonNull MotionEvent event) {
MotionEvent copy = MotionEvent.obtain(event);
copy.offsetLocation(-getContainerView().getX(), -getContainerView().getY());
// Dismiss the panel if we exit outside of the range
if(copy.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
if (mKeyDetector.detectHitKey(
(int)copy.getX(),
(int)copy.getY()
) == null) {
dismissMoreKeysPanel();
}
return true;
} else {
return MoreKeysKeyboardView.this.dispatchHoverEvent(copy);
}
}
} }
@Override @Override
@ -323,6 +368,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
final ViewGroup currentParent = (ViewGroup)containerView.getParent(); final ViewGroup currentParent = (ViewGroup)containerView.getParent();
if (currentParent != null) { if (currentParent != null) {
currentParent.removeView(containerView); currentParent.removeView(containerView);
currentParent.setTouchDelegate(null);
} }
} }