diff options
Diffstat (limited to 'java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java')
-rw-r--r-- | java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java | 334 |
1 files changed, 214 insertions, 120 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java index 96f7fc9f2..f6376d5f4 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java @@ -17,185 +17,279 @@ package com.android.inputmethod.accessibility; import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Color; -import android.graphics.Paint; -import android.util.Log; +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.compat.AccessibilityEventCompatUtils; -import com.android.inputmethod.compat.MotionEventCompatUtils; import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.KeyDetector; -import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.LatinKeyboardView; import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.latin.R; -public class AccessibleKeyboardViewProxy { - private static final String TAG = AccessibleKeyboardViewProxy.class.getSimpleName(); +public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy(); - // Delay in milliseconds between key press DOWN and UP events - private static final long DELAY_KEY_PRESS = 10; + private InputMethodService mInputMethod; + private LatinKeyboardView mView; + private AccessibilityEntityProvider mAccessibilityNodeProvider; - private int mScaledEdgeSlop; - private KeyboardView mView; - private AccessibleKeyboardActionListener mListener; + private Key mLastHoverKey = null; - private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY; - private int mLastX = -1; - private int mLastY = -1; + /** + * Inset in pixels to look for keys when the user's finger exits the + * keyboard area. See {@link ViewConfiguration#getScaledEdgeSlop()}. + */ + private int mEdgeSlop; - public static void init(Context context, SharedPreferences prefs) { - sInstance.initInternal(context, prefs); - sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance(); + public static void init(InputMethodService inputMethod) { + sInstance.initInternal(inputMethod); } public static AccessibleKeyboardViewProxy getInstance() { return sInstance; } - public static void setView(KeyboardView view) { - sInstance.mView = view; - } - private AccessibleKeyboardViewProxy() { // Not publicly instantiable. } - private void initInternal(Context context, SharedPreferences prefs) { - final Paint paint = new Paint(); - paint.setTextAlign(Paint.Align.LEFT); - paint.setTextSize(14.0f); - paint.setAntiAlias(true); - paint.setColor(Color.YELLOW); - - mScaledEdgeSlop = ViewConfiguration.get(context).getScaledEdgeSlop(); + private void initInternal(InputMethodService inputMethod) { + mInputMethod = inputMethod; + mEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop(); } - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event, - PointerTracker tracker) { - if (mView == null) { - Log.e(TAG, "No keyboard view set!"); - return false; + /** + * Sets the view wrapped by this proxy. + * + * @param view The view to wrap. + */ + public void setView(LatinKeyboardView view) { + if (view == null) { + // Ignore null views. + return; } - switch (event.getEventType()) { - case AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER: - final Key key = tracker.getKey(mLastHoverKeyIndex); + mView = view; - if (key == null) - break; + // Ensure that the view has an accessibility delegate. + ViewCompat.setAccessibilityDelegate(view, this); - final CharSequence description = KeyCodeDescriptionMapper.getInstance() - .getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key); - - if (description == null) - return false; - - event.getText().add(description); - - break; + if (mAccessibilityNodeProvider != null) { + mAccessibilityNodeProvider.setView(view); } + } - return true; + public void setKeyboard(Keyboard keyboard) { + if (mAccessibilityNodeProvider != null) { + mAccessibilityNodeProvider.setKeyboard(keyboard); + } } /** - * Receives hover events when accessibility is turned on in API > 11. In - * earlier API levels, events are manually routed from onTouchEvent. + * Proxy method for View.getAccessibilityNodeProvider(). This method is + * called in SDK version 15 and higher to obtain the virtual node hierarchy + * provider. * - * @param event The hover event. - * @return {@code true} if the event is handled + * @return The accessibility node provider for the current keyboard. */ - public boolean onHoverEvent(MotionEvent event, PointerTracker tracker) { - return onTouchExplorationEvent(event, tracker); - } - - public boolean dispatchTouchEvent(MotionEvent event) { - // Since touch exploration translates hover double-tap to a regular - // single-tap, we're going to drop non-touch exploration events. - if (!AccessibilityUtils.getInstance().isTouchExplorationEvent(event)) - return true; - - return false; + @Override + public AccessibilityEntityProvider getAccessibilityNodeProvider(View host) { + return getAccessibilityNodeProvider(); } /** - * Handles touch exploration events when Accessibility is turned on. + * Receives hover events when accessibility is turned on in SDK versions ICS + * and higher. * - * @param event The touch exploration hover event. - * @return {@code true} if the event was handled + * @param event The hover event. + * @return {@code true} if the event is handled */ - private boolean onTouchExplorationEvent(MotionEvent event, PointerTracker tracker) { + public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) { final int x = (int) event.getX(); final int y = (int) event.getY(); + final Key key = tracker.getKeyOn(x, y); + final Key previousKey = mLastHoverKey; + + mLastHoverKey = key; switch (event.getAction()) { - case MotionEventCompatUtils.ACTION_HOVER_ENTER: - case MotionEventCompatUtils.ACTION_HOVER_MOVE: - final int keyIndex = tracker.getKeyIndexOn(x, y); - - if (keyIndex != mLastHoverKeyIndex) { - fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false); - mLastHoverKeyIndex = keyIndex; - mLastX = x; - mLastY = y; - fireKeyHoverEvent(tracker, mLastHoverKeyIndex, true); + case MotionEvent.ACTION_HOVER_EXIT: + // 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) && (key != null)) { + getAccessibilityNodeProvider().simulateKeyPress(key); } - - return true; - case MotionEventCompatUtils.ACTION_HOVER_EXIT: - final int width = mView.getWidth(); - final int height = mView.getHeight(); - - if (x < mScaledEdgeSlop || y < mScaledEdgeSlop || x >= (width - mScaledEdgeSlop) - || y >= (height - mScaledEdgeSlop)) { - fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false); - mLastHoverKeyIndex = KeyDetector.NOT_A_KEY; - mLastX = -1; - mLastY = -1; - } else if (mLastHoverKeyIndex != KeyDetector.NOT_A_KEY) { - fireKeyPressEvent(tracker, mLastX, mLastY, event.getEventTime()); + //$FALL-THROUGH$ + case MotionEvent.ACTION_HOVER_ENTER: + return onHoverKey(key, event); + case MotionEvent.ACTION_HOVER_MOVE: + if (key != previousKey) { + return onTransitionKey(key, previousKey, event); + } else { + return onHoverKey(key, event); } - - return true; } return false; } - private void fireKeyHoverEvent(PointerTracker tracker, int keyIndex, boolean entering) { - if (mListener == null) { - Log.e(TAG, "No accessible keyboard action listener set!"); - return; + /** + * @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; + } - if (mView == null) { - Log.e(TAG, "No keyboard view set!"); - return; + /** + * Utility method to determine whether the given point, in local + * coordinates, is inside the view, where the area of the view is contracted + * by the edge slop factor. + * + * @param localX The local x-coordinate. + * @param localY The local y-coordinate. + */ + private boolean pointInView(int localX, int localY) { + return (localX >= mEdgeSlop) && (localY >= mEdgeSlop) + && (localX < (mView.getWidth() - mEdgeSlop)) + && (localY < (mView.getHeight() - mEdgeSlop)); + } + + /** + * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT + * on the previous key, a HOVER_ENTER on the current key, and a HOVER_MOVE + * on the current key. + * + * @param currentKey The currently hovered key. + * @param previousKey The previously hovered key. + * @param event The event that triggered the transition. + * @return {@code true} if the event was handled. + */ + private boolean onTransitionKey(Key currentKey, Key previousKey, MotionEvent event) { + final int savedAction = event.getAction(); + + event.setAction(MotionEvent.ACTION_HOVER_EXIT); + onHoverKey(previousKey, event); + + event.setAction(MotionEvent.ACTION_HOVER_ENTER); + onHoverKey(currentKey, event); + + event.setAction(MotionEvent.ACTION_HOVER_MOVE); + final boolean handled = onHoverKey(currentKey, event); + + event.setAction(savedAction); + + return handled; + } + + /** + * Handles a hover event on a key. If {@link Key} extended View, this would + * be analogous to calling View.onHoverEvent(MotionEvent). + * + * @param key The currently hovered key. + * @param event The hover event. + * @return {@code true} if the event was handled. + */ + private boolean onHoverKey(Key key, MotionEvent event) { + // Null keys can't receive events. + if (key == null) { + return false; } - if (keyIndex == KeyDetector.NOT_A_KEY) - return; + final AccessibilityEntityProvider provider = getAccessibilityNodeProvider(); - final Key key = tracker.getKey(keyIndex); + switch (event.getAction()) { + case MotionEvent.ACTION_HOVER_ENTER: + provider.sendAccessibilityEventForKey( + key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER); + provider.performActionForKey( + key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null); + break; + case MotionEvent.ACTION_HOVER_EXIT: + provider.sendAccessibilityEventForKey( + key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT); + break; + } - if (key == null) - return; + return true; + } - if (entering) { - mListener.onHoverEnter(key.mCode); - mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER); - } else { - mListener.onHoverExit(key.mCode); - mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_EXIT); + /** + * Notifies the user of changes in the keyboard shift state. + */ + public void notifyShiftState() { + final Keyboard keyboard = mView.getKeyboard(); + final KeyboardId keyboardId = keyboard.mId; + final int elementId = keyboardId.mElementId; + final Context context = mView.getContext(); + final CharSequence text; + + switch (elementId) { + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: + text = context.getText(R.string.spoken_description_shiftmode_locked); + break; + case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: + case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: + text = context.getText(R.string.spoken_description_shiftmode_on); + break; + default: + text = context.getText(R.string.spoken_description_shiftmode_off); } + + AccessibilityUtils.getInstance().speak(text); } - private void fireKeyPressEvent(PointerTracker tracker, int x, int y, long eventTime) { - tracker.onDownEvent(x, y, eventTime, null); - tracker.onUpEvent(x, y, eventTime + DELAY_KEY_PRESS, null); + /** + * Notifies the user of changes in the keyboard symbols state. + */ + public void notifySymbolsState() { + final Keyboard keyboard = mView.getKeyboard(); + final Context context = mView.getContext(); + final KeyboardId keyboardId = keyboard.mId; + final int elementId = keyboardId.mElementId; + final int resId; + + switch (elementId) { + case KeyboardId.ELEMENT_ALPHABET: + case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: + resId = R.string.spoken_description_mode_alpha; + break; + case KeyboardId.ELEMENT_SYMBOLS: + case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: + resId = R.string.spoken_description_mode_symbol; + break; + case KeyboardId.ELEMENT_PHONE: + resId = R.string.spoken_description_mode_phone; + break; + case KeyboardId.ELEMENT_PHONE_SYMBOLS: + resId = R.string.spoken_description_mode_phone_shift; + break; + default: + resId = -1; + } + + if (resId < 0) { + return; + } + + final String text = context.getString(resId); + AccessibilityUtils.getInstance().speak(text); } } |