Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f2eba97c authored by alanv's avatar alanv
Browse files

Make LatinIME keys accessibility focusable, clickable.

Also fix speech for labeled keys.

Bug: 6498563
Change-Id: I094d4db0e57fa373759a63eb3354b1ab3ab0f525
parent b4b3e80f
Loading
Loading
Loading
Loading
+115 −1
Original line number Diff line number Diff line
@@ -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}.
     *
+24 −25
Original line number Diff line number Diff line
@@ -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.
     */
+3 −0
Original line number Diff line number Diff line
@@ -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.