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

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
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.