Make LatinIME keys accessibility focusable, clickable.
Also fix speech for labeled keys. Bug: 6498563 Change-Id: I094d4db0e57fa373759a63eb3354b1ab3ab0f525main
parent
b4b3e80f11
commit
f2eba97cc0
|
@ -18,13 +18,18 @@ package com.android.inputmethod.accessibility;
|
|||
|
||||
import android.graphics.Rect;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
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.AccessibilityNodeProviderCompat;
|
||||
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
@ -45,6 +50,7 @@ import com.android.inputmethod.keyboard.KeyboardView;
|
|||
*/
|
||||
public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
|
||||
private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
|
||||
private static final int UNDEFINED = Integer.MIN_VALUE;
|
||||
|
||||
private final KeyboardView mKeyboardView;
|
||||
private final InputMethodService mInputMethodService;
|
||||
|
@ -60,6 +66,9 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
|
|||
/** The parent view's cached on-screen location. */
|
||||
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) {
|
||||
mKeyboardView = keyboardView;
|
||||
mInputMethodService = inputMethod;
|
||||
|
@ -124,7 +133,9 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
|
|||
public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
|
||||
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
|
||||
// this View, i.e. the root of the virtual sub-tree.
|
||||
info = AccessibilityNodeInfoCompat.obtain(mKeyboardView);
|
||||
|
@ -166,11 +177,114 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
|
|||
info.setSource(mKeyboardView, virtualViewId);
|
||||
info.setBoundsInScreen(boundsInScreen);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
|
|
|
@ -21,10 +21,10 @@ import android.inputmethodservice.InputMethodService;
|
|||
import android.support.v4.view.AccessibilityDelegateCompat;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
||||
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import com.android.inputmethod.keyboard.Key;
|
||||
import com.android.inputmethod.keyboard.Keyboard;
|
||||
|
@ -91,13 +91,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
*/
|
||||
@Override
|
||||
public AccessibilityEntityProvider getAccessibilityNodeProvider(View host) {
|
||||
// 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;
|
||||
return getAccessibilityNodeProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,7 +114,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
// Make sure we're not getting an EXIT event because the user slid
|
||||
// off the keyboard area, then force a key press.
|
||||
if (pointInView(x, y)) {
|
||||
tracker.onRegisterKey(key);
|
||||
getAccessibilityNodeProvider().simulateKeyPress(key);
|
||||
}
|
||||
//$FALL-THROUGH$
|
||||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
|
@ -136,6 +130,19 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
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
|
||||
* coordinates, is inside the view, where the area of the view is contracted
|
||||
|
@ -191,32 +198,24 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
return false;
|
||||
}
|
||||
|
||||
final AccessibilityEntityProvider provider = getAccessibilityNodeProvider();
|
||||
|
||||
switch (event.getAction()) {
|
||||
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;
|
||||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
|
||||
provider.sendAccessibilityEventForKey(
|
||||
key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
|
||||
break;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -111,6 +111,9 @@ public class KeyCodeDescriptionMapper {
|
|||
if (mKeyLabelMap.containsKey(label)) {
|
||||
return context.getString(mKeyLabelMap.get(label));
|
||||
}
|
||||
|
||||
// Otherwise, return the label.
|
||||
return key.mLabel;
|
||||
}
|
||||
|
||||
// Just attempt to speak the description.
|
||||
|
|
Loading…
Reference in New Issue