Make LatinIME keys accessibility focusable, clickable.
Also fix speech for labeled keys. Bug: 6498563 Change-Id: I094d4db0e57fa373759a63eb3354b1ab3ab0f525
This commit is contained in:
parent
b4b3e80f11
commit
f2eba97cc0
3 changed files with 142 additions and 26 deletions
|
@ -18,13 +18,18 @@ package com.android.inputmethod.accessibility;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.inputmethodservice.InputMethodService;
|
import android.inputmethodservice.InputMethodService;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.support.v4.view.ViewCompat;
|
import android.support.v4.view.ViewCompat;
|
||||||
|
import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
||||||
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
||||||
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
|
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
|
||||||
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
|
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewParent;
|
||||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
@ -45,6 +50,7 @@ import com.android.inputmethod.keyboard.KeyboardView;
|
||||||
*/
|
*/
|
||||||
public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
|
public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
|
||||||
private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
|
private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
|
||||||
|
private static final int UNDEFINED = Integer.MIN_VALUE;
|
||||||
|
|
||||||
private final KeyboardView mKeyboardView;
|
private final KeyboardView mKeyboardView;
|
||||||
private final InputMethodService mInputMethodService;
|
private final InputMethodService mInputMethodService;
|
||||||
|
@ -60,6 +66,9 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
|
||||||
/** The parent view's cached on-screen location. */
|
/** The parent view's cached on-screen location. */
|
||||||
private final int[] mParentLocation = new int[2];
|
private final int[] mParentLocation = new int[2];
|
||||||
|
|
||||||
|
/** The virtual view identifier for the focused node. */
|
||||||
|
private int mAccessibilityFocusedView = UNDEFINED;
|
||||||
|
|
||||||
public AccessibilityEntityProvider(KeyboardView keyboardView, InputMethodService inputMethod) {
|
public AccessibilityEntityProvider(KeyboardView keyboardView, InputMethodService inputMethod) {
|
||||||
mKeyboardView = keyboardView;
|
mKeyboardView = keyboardView;
|
||||||
mInputMethodService = inputMethod;
|
mInputMethodService = inputMethod;
|
||||||
|
@ -124,7 +133,9 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
|
||||||
public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
|
public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
|
||||||
AccessibilityNodeInfoCompat info = null;
|
AccessibilityNodeInfoCompat info = null;
|
||||||
|
|
||||||
if (virtualViewId == View.NO_ID) {
|
if (virtualViewId == UNDEFINED) {
|
||||||
|
return null;
|
||||||
|
} else if (virtualViewId == View.NO_ID) {
|
||||||
// We are requested to create an AccessibilityNodeInfo describing
|
// We are requested to create an AccessibilityNodeInfo describing
|
||||||
// this View, i.e. the root of the virtual sub-tree.
|
// this View, i.e. the root of the virtual sub-tree.
|
||||||
info = AccessibilityNodeInfoCompat.obtain(mKeyboardView);
|
info = AccessibilityNodeInfoCompat.obtain(mKeyboardView);
|
||||||
|
@ -166,11 +177,114 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
|
||||||
info.setSource(mKeyboardView, virtualViewId);
|
info.setSource(mKeyboardView, virtualViewId);
|
||||||
info.setBoundsInScreen(boundsInScreen);
|
info.setBoundsInScreen(boundsInScreen);
|
||||||
info.setEnabled(true);
|
info.setEnabled(true);
|
||||||
|
info.setClickable(true);
|
||||||
|
info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
|
||||||
|
|
||||||
|
if (mAccessibilityFocusedView == virtualViewId) {
|
||||||
|
info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
|
||||||
|
} else {
|
||||||
|
info.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates a key press by injecting touch events into the keyboard view.
|
||||||
|
* This avoids the complexity of trackers and listeners within the keyboard.
|
||||||
|
*
|
||||||
|
* @param key The key to press.
|
||||||
|
*/
|
||||||
|
void simulateKeyPress(Key key) {
|
||||||
|
final int x = key.mX + (key.mWidth / 2);
|
||||||
|
final int y = key.mY + (key.mHeight / 2);
|
||||||
|
final long downTime = SystemClock.uptimeMillis();
|
||||||
|
final MotionEvent downEvent = MotionEvent.obtain(
|
||||||
|
downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
|
||||||
|
final MotionEvent upEvent = MotionEvent.obtain(
|
||||||
|
downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);
|
||||||
|
|
||||||
|
mKeyboardView.onTouchEvent(downEvent);
|
||||||
|
mKeyboardView.onTouchEvent(upEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean performAction(int virtualViewId, int action, Bundle arguments) {
|
||||||
|
final Key key = mVirtualViewIdToKey.get(virtualViewId);
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return performActionForKey(key, action, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the specified accessibility action for the given key.
|
||||||
|
*
|
||||||
|
* @param key The on which to perform the action.
|
||||||
|
* @param action The action to perform.
|
||||||
|
* @param arguments The action's arguments.
|
||||||
|
* @return The result of performing the action, or false if the action is
|
||||||
|
* not supported.
|
||||||
|
*/
|
||||||
|
boolean performActionForKey(Key key, int action, Bundle arguments) {
|
||||||
|
final int virtualViewId = generateVirtualViewIdForKey(key);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case AccessibilityNodeInfoCompat.ACTION_CLICK:
|
||||||
|
simulateKeyPress(key);
|
||||||
|
return true;
|
||||||
|
case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
|
||||||
|
if (mAccessibilityFocusedView == virtualViewId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mAccessibilityFocusedView = virtualViewId;
|
||||||
|
sendAccessibilityEventForKey(
|
||||||
|
key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
|
||||||
|
return true;
|
||||||
|
case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
|
||||||
|
if (mAccessibilityFocusedView != virtualViewId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mAccessibilityFocusedView = UNDEFINED;
|
||||||
|
sendAccessibilityEventForKey(
|
||||||
|
key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AccessibilityNodeInfoCompat findAccessibilityFocus(int virtualViewId) {
|
||||||
|
return createAccessibilityNodeInfo(mAccessibilityFocusedView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AccessibilityNodeInfoCompat accessibilityFocusSearch(int direction, int virtualViewId) {
|
||||||
|
// Focus search is not currently supported for IMEs.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an accessibility event for the given {@link Key}.
|
||||||
|
*
|
||||||
|
* @param key The key that's sending the event.
|
||||||
|
* @param eventType The type of event to send.
|
||||||
|
*/
|
||||||
|
void sendAccessibilityEventForKey(Key key, int eventType) {
|
||||||
|
final AccessibilityEvent event = createAccessibilityEvent(key, eventType);
|
||||||
|
final ViewParent parent = mKeyboardView.getParent();
|
||||||
|
|
||||||
|
if (parent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.requestSendAccessibilityEvent(mKeyboardView, event);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the context-specific description for a {@link Key}.
|
* Returns the context-specific description for a {@link Key}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -21,10 +21,10 @@ import android.inputmethodservice.InputMethodService;
|
||||||
import android.support.v4.view.AccessibilityDelegateCompat;
|
import android.support.v4.view.AccessibilityDelegateCompat;
|
||||||
import android.support.v4.view.ViewCompat;
|
import android.support.v4.view.ViewCompat;
|
||||||
import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
||||||
|
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.Key;
|
import com.android.inputmethod.keyboard.Key;
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
|
@ -91,13 +91,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public AccessibilityEntityProvider getAccessibilityNodeProvider(View host) {
|
public AccessibilityEntityProvider getAccessibilityNodeProvider(View host) {
|
||||||
// Instantiate the provide only when requested. Since the system
|
return getAccessibilityNodeProvider();
|
||||||
// will call this method multiple times it is a good practice to
|
|
||||||
// cache the provider instance.
|
|
||||||
if (mAccessibilityNodeProvider == null) {
|
|
||||||
mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod);
|
|
||||||
}
|
|
||||||
return mAccessibilityNodeProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,7 +114,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
// Make sure we're not getting an EXIT event because the user slid
|
// Make sure we're not getting an EXIT event because the user slid
|
||||||
// off the keyboard area, then force a key press.
|
// off the keyboard area, then force a key press.
|
||||||
if (pointInView(x, y)) {
|
if (pointInView(x, y)) {
|
||||||
tracker.onRegisterKey(key);
|
getAccessibilityNodeProvider().simulateKeyPress(key);
|
||||||
}
|
}
|
||||||
//$FALL-THROUGH$
|
//$FALL-THROUGH$
|
||||||
case MotionEvent.ACTION_HOVER_ENTER:
|
case MotionEvent.ACTION_HOVER_ENTER:
|
||||||
|
@ -136,6 +130,19 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A lazily-instantiated node provider for this view proxy.
|
||||||
|
*/
|
||||||
|
private AccessibilityEntityProvider getAccessibilityNodeProvider() {
|
||||||
|
// Instantiate the provide only when requested. Since the system
|
||||||
|
// will call this method multiple times it is a good practice to
|
||||||
|
// cache the provider instance.
|
||||||
|
if (mAccessibilityNodeProvider == null) {
|
||||||
|
mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod);
|
||||||
|
}
|
||||||
|
return mAccessibilityNodeProvider;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to determine whether the given point, in local
|
* Utility method to determine whether the given point, in local
|
||||||
* coordinates, is inside the view, where the area of the view is contracted
|
* coordinates, is inside the view, where the area of the view is contracted
|
||||||
|
@ -191,32 +198,24 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final AccessibilityEntityProvider provider = getAccessibilityNodeProvider();
|
||||||
|
|
||||||
switch (event.getAction()) {
|
switch (event.getAction()) {
|
||||||
case MotionEvent.ACTION_HOVER_ENTER:
|
case MotionEvent.ACTION_HOVER_ENTER:
|
||||||
sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
|
provider.sendAccessibilityEventForKey(
|
||||||
|
key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
|
||||||
|
provider.performActionForKey(
|
||||||
|
key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_HOVER_EXIT:
|
case MotionEvent.ACTION_HOVER_EXIT:
|
||||||
sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
|
provider.sendAccessibilityEventForKey(
|
||||||
|
key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates and sends an {@link AccessibilityEvent} for the specified key.
|
|
||||||
*
|
|
||||||
* @param key The key to send an event for.
|
|
||||||
* @param eventType The type of event to send.
|
|
||||||
*/
|
|
||||||
private void sendAccessibilityEventForKey(Key key, int eventType) {
|
|
||||||
final AccessibilityEntityProvider nodeProvider = getAccessibilityNodeProvider(null);
|
|
||||||
final AccessibilityEvent event = nodeProvider.createAccessibilityEvent(key, eventType);
|
|
||||||
|
|
||||||
// Propagates the event up the view hierarchy.
|
|
||||||
mView.getParent().requestSendAccessibilityEvent(mView, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the user of changes in the keyboard shift state.
|
* Notifies the user of changes in the keyboard shift state.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -111,6 +111,9 @@ public class KeyCodeDescriptionMapper {
|
||||||
if (mKeyLabelMap.containsKey(label)) {
|
if (mKeyLabelMap.containsKey(label)) {
|
||||||
return context.getString(mKeyLabelMap.get(label));
|
return context.getString(mKeyLabelMap.get(label));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, return the label.
|
||||||
|
return key.mLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just attempt to speak the description.
|
// Just attempt to speak the description.
|
||||||
|
|
Loading…
Reference in a new issue