Skip to content
Snippets Groups Projects
Commit 928eec5c authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android (Google) Code Review
Browse files

Merge "Make LatinIME keys accessibility focusable, clickable." into jb-dev

parents e78218b8 f2eba97c
No related branches found
No related tags found
No related merge requests found
...@@ -18,13 +18,18 @@ package com.android.inputmethod.accessibility; ...@@ -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; ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment