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

Commit b01c3d2b authored by Svetoslav Ganov's avatar Svetoslav Ganov
Browse files

WebView accessibility key bindings do not allow mapping of all meta keys....

WebView accessibility key bindings do not allow mapping of all meta keys. Exception when turning off acessibility and having a WebView showing content.

bug:3341772

1.  Now a key in the binding is represented as a long with 32 MSB for modifiers and 32 LSB for keyCode.

bug: 3340732

1. Added check in the WebView key handling code to diable the accessibility injector/injected script in
   case accessibility is been disabled after loading the content.

Change-Id: Ic3746dff16ec77ba682a5b139cec0e6afd8fc839
parent 9fafe4e0
Loading
Loading
Loading
Loading
+46 −65
Original line number Original line Diff line number Diff line
@@ -20,7 +20,6 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.Log;
import android.util.Log;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager;
@@ -41,7 +40,7 @@ import java.util.Stack;
 *       aware of one navigation axis which is in fact the default behavior
 *       aware of one navigation axis which is in fact the default behavior
 *       of webViews while using the DPAD/TrackBall.
 *       of webViews while using the DPAD/TrackBall.
 * </p>
 * </p>
 * In general a key binding is a mapping from meta state + key code to
 * In general a key binding is a mapping from modifiers + key code to
 * a sequence of actions. For more detail how to specify key bindings refer to
 * a sequence of actions. For more detail how to specify key bindings refer to
 * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
 * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
 * </p>
 * </p>
@@ -77,8 +76,8 @@ class AccessibilityInjector {
    private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
    private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;


    // these are the same for all instances so make them process wide
    // these are the same for all instances so make them process wide
    private static SparseArray<AccessibilityWebContentKeyBinding> sBindings =
    private static ArrayList<AccessibilityWebContentKeyBinding> sBindings =
        new SparseArray<AccessibilityWebContentKeyBinding>();
        new ArrayList<AccessibilityWebContentKeyBinding>();


    // handle to the WebView this injector is associated with.
    // handle to the WebView this injector is associated with.
    private final WebView mWebView;
    private final WebView mWebView;
@@ -120,10 +119,15 @@ class AccessibilityInjector {


        mLastDownEventHandled = false;
        mLastDownEventHandled = false;


        int key = event.getMetaState() << AccessibilityWebContentKeyBinding.OFFSET_META_STATE |
        AccessibilityWebContentKeyBinding binding = null;
            event.getKeyCode() << AccessibilityWebContentKeyBinding.OFFSET_KEY_CODE;
        for (AccessibilityWebContentKeyBinding candidate : sBindings) {
            if (event.getKeyCode() == candidate.getKeyCode()
                    && event.hasModifiers(candidate.getModifiers())) {
                binding = candidate;
                break;
            }
        }


        AccessibilityWebContentKeyBinding binding = sBindings.get(key);
        if (binding == null) {
        if (binding == null) {
            return false;
            return false;
        }
        }
@@ -302,7 +306,12 @@ class AccessibilityInjector {
        if (DEBUG) {
        if (DEBUG) {
            Log.d(LOG_TAG, "Dispatching: " + event);
            Log.d(LOG_TAG, "Dispatching: " + event);
        }
        }
        AccessibilityManager.getInstance(mWebView.getContext()).sendAccessibilityEvent(event);
        // accessibility may be disabled while waiting for the selection string
        AccessibilityManager accessibilityManager =
            AccessibilityManager.getInstance(mWebView.getContext());
        if (accessibilityManager.isEnabled()) {
            accessibilityManager.sendAccessibilityEvent(event);
        }
    }
    }


    /**
    /**
@@ -332,9 +341,6 @@ class AccessibilityInjector {
        SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
        SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
        semiColonSplitter.setString(webContentKeyBindingsString);
        semiColonSplitter.setString(webContentKeyBindingsString);


        ArrayList<AccessibilityWebContentKeyBinding> bindings =
            new ArrayList<AccessibilityWebContentKeyBinding>();

        while (semiColonSplitter.hasNext()) {
        while (semiColonSplitter.hasNext()) {
            String bindingString = semiColonSplitter.next();
            String bindingString = semiColonSplitter.next();
            if (TextUtils.isEmpty(bindingString)) {
            if (TextUtils.isEmpty(bindingString)) {
@@ -348,80 +354,58 @@ class AccessibilityInjector {
                continue;
                continue;
            }
            }
            try {
            try {
                int key = Integer.decode(keyValueArray[0].trim());
                long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim());
                String[] actionStrings = keyValueArray[1].split(":");
                String[] actionStrings = keyValueArray[1].split(":");
                int[] actions = new int[actionStrings.length];
                int[] actions = new int[actionStrings.length];
                for (int i = 0, count = actions.length; i < count; i++) {
                for (int i = 0, count = actions.length; i < count; i++) {
                    actions[i] = Integer.decode(actionStrings[i].trim());
                    actions[i] = Integer.decode(actionStrings[i].trim());
                }
                }
                bindings.add(new AccessibilityWebContentKeyBinding(key, actions));
                sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions));
            } catch (NumberFormatException nfe) {
            } catch (NumberFormatException nfe) {
                Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
                Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
            }
            }
        }
        }

        for (AccessibilityWebContentKeyBinding binding : bindings) {
            sBindings.put(binding.getKey(), binding);
        }
    }
    }


    /**
    /**
     * Represents a web content key-binding.
     * Represents a web content key-binding.
     */
     */
    private class AccessibilityWebContentKeyBinding {
    private static final class AccessibilityWebContentKeyBinding {

        private static final int OFFSET_META_STATE = 0x00000010;


        private static final int MASK_META_STATE = 0xFFFF0000;
        private static final int MODIFIERS_OFFSET = 32;
        private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L;


        private static final int OFFSET_KEY_CODE = 0x00000000;
        private static final int KEY_CODE_OFFSET = 0;
        private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL;


        private static final int MASK_KEY_CODE = 0x0000FFFF;
        private static final int ACTION_OFFSET = 24;
        private static final int ACTION_MASK = 0xFF000000;


        private static final int OFFSET_ACTION = 0x00000018;
        private static final int FIRST_ARGUMENT_OFFSET = 16;
        private static final int FIRST_ARGUMENT_MASK = 0x00FF0000;


        private static final int MASK_ACTION = 0xFF000000;
        private static final int SECOND_ARGUMENT_OFFSET = 8;
        private static final int SECOND_ARGUMENT_MASK = 0x0000FF00;


        private static final int OFFSET_FIRST_ARGUMENT = 0x00000010;
        private static final int THIRD_ARGUMENT_OFFSET = 0;
        private static final int THIRD_ARGUMENT_MASK = 0x000000FF;


        private static final int MASK_FIRST_ARGUMENT = 0x00FF0000;
        private final long mKeyCodeAndModifiers;


        private static final int OFFSET_SECOND_ARGUMENT = 0x00000008;
        private final int [] mActionSequence;

        private static final int MASK_SECOND_ARGUMENT = 0x0000FF00;

        private static final int OFFSET_THIRD_ARGUMENT = 0x00000000;

        private static final int MASK_THIRD_ARGUMENT = 0x000000FF;

        private int mKey;

        private int [] mActionSequence;

        /**
         * @return The binding key with key code and meta state.
         *
         * @see #MASK_KEY_CODE
         * @see #MASK_META_STATE
         * @see #OFFSET_KEY_CODE
         * @see #OFFSET_META_STATE
         */
        public int getKey() {
            return mKey;
        }


        /**
        /**
         * @return The key code of the binding key.
         * @return The key code of the binding key.
         */
         */
        public int getKeyCode() {
        public int getKeyCode() {
            return (mKey & MASK_KEY_CODE) >> OFFSET_KEY_CODE;
            return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET);
        }
        }


        /**
        /**
         * @return The meta state of the binding key.
         * @return The meta state of the binding key.
         */
         */
        public int getMetaState() {
        public int getModifiers() {
            return (mKey & MASK_META_STATE) >> OFFSET_META_STATE;
            return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET);
        }
        }


        /**
        /**
@@ -442,48 +426,45 @@ class AccessibilityInjector {
         * @param index The action code for a given action <code>index</code>.
         * @param index The action code for a given action <code>index</code>.
         */
         */
        public int getActionCode(int index) {
        public int getActionCode(int index) {
            return (mActionSequence[index] & MASK_ACTION) >> OFFSET_ACTION;
            return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET;
        }
        }


        /**
        /**
         * @param index The first argument for a given action <code>index</code>.
         * @param index The first argument for a given action <code>index</code>.
         */
         */
        public int getFirstArgument(int index) {
        public int getFirstArgument(int index) {
            return (mActionSequence[index] & MASK_FIRST_ARGUMENT) >> OFFSET_FIRST_ARGUMENT;
            return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET;
        }
        }


        /**
        /**
         * @param index The second argument for a given action <code>index</code>.
         * @param index The second argument for a given action <code>index</code>.
         */
         */
        public int getSecondArgument(int index) {
        public int getSecondArgument(int index) {
            return (mActionSequence[index] & MASK_SECOND_ARGUMENT) >> OFFSET_SECOND_ARGUMENT;
            return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET;
        }
        }


        /**
        /**
         * @param index The third argument for a given action <code>index</code>.
         * @param index The third argument for a given action <code>index</code>.
         */
         */
        public int getThirdArgument(int index) {
        public int getThirdArgument(int index) {
            return (mActionSequence[index] & MASK_THIRD_ARGUMENT) >> OFFSET_THIRD_ARGUMENT;
            return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET;
        }
        }


        /**
        /**
         * Creates a new instance.
         * Creates a new instance.
         * @param key The key for the binding (key and meta state)
         * @param keyCodeAndModifiers The key for the binding (key and modifiers).
         * @param actionSequence The sequence of action for the binding.
         * @param actionSequence The sequence of action for the binding.
         * @see #getKey()
         */
         */
        public AccessibilityWebContentKeyBinding(int key, int[] actionSequence) {
        public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) {
            mKey = key;
            mKeyCodeAndModifiers = keyCodeAndModifiers;
            mActionSequence = actionSequence;
            mActionSequence = actionSequence;
        }
        }


        @Override
        @Override
        public String toString() {
        public String toString() {
            StringBuilder builder = new StringBuilder();
            StringBuilder builder = new StringBuilder();
            builder.append("key: ");
            builder.append("modifiers: ");
            builder.append(getKey());
            builder.append(getModifiers());
            builder.append(", metaState: ");
            builder.append(getMetaState());
            builder.append(", keyCode: ");
            builder.append(", keyCode: ");
            builder.append(getKeyCode());
            builder.append(getKeyCode());
            builder.append(", actions[");
            builder.append(", actions[");
+44 −20
Original line number Original line Diff line number Diff line
@@ -4545,17 +4545,29 @@ public class WebView extends AbsoluteLayout


        // accessibility support
        // accessibility support
        if (accessibilityScriptInjected()) {
        if (accessibilityScriptInjected()) {
            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
                // if an accessibility script is injected we delegate to it the key handling.
                // if an accessibility script is injected we delegate to it the key handling.
                // this script is a screen reader which is a fully fledged solution for blind
                // this script is a screen reader which is a fully fledged solution for blind
                // users to navigate in and interact with web pages.
                // users to navigate in and interact with web pages.
                mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
                mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
                return true;
                return true;
        } else if (mAccessibilityInjector != null && mAccessibilityInjector.onKeyEvent(event)) {
            } else {
            // if an accessibility injector is present (no JavaScript enabled or the site opts
                // Clean up if accessibility was disabled after loading the current URL.
            // out injecting our JavaScript screen reader) we let it decide whether to act on
                mAccessibilityScriptInjected = false;
            // and consume the event.
            }
        } else if (mAccessibilityInjector != null) {
            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
                if (mAccessibilityInjector.onKeyEvent(event)) {
                    // if an accessibility injector is present (no JavaScript enabled or the site
                    // opts out injecting our JavaScript screen reader) we let it decide whether
                    // to act on and consume the event.
                    return true;
                    return true;
                }
                }
            } else {
                // Clean up if accessibility was disabled after loading the current URL.
                mAccessibilityInjector = null;
            }
        }


        if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
        if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
            if (event.hasNoModifiers()) {
            if (event.hasNoModifiers()) {
@@ -4733,17 +4745,29 @@ public class WebView extends AbsoluteLayout


        // accessibility support
        // accessibility support
        if (accessibilityScriptInjected()) {
        if (accessibilityScriptInjected()) {
            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
                // if an accessibility script is injected we delegate to it the key handling.
                // if an accessibility script is injected we delegate to it the key handling.
                // this script is a screen reader which is a fully fledged solution for blind
                // this script is a screen reader which is a fully fledged solution for blind
                // users to navigate in and interact with web pages.
                // users to navigate in and interact with web pages.
                mWebViewCore.sendMessage(EventHub.KEY_UP, event);
                mWebViewCore.sendMessage(EventHub.KEY_UP, event);
                return true;
                return true;
        } else if (mAccessibilityInjector != null && mAccessibilityInjector.onKeyEvent(event)) {
            } else {
            // if an accessibility injector is present (no JavaScript enabled or the site opts
                // Clean up if accessibility was disabled after loading the current URL.
            // out injecting our JavaScript screen reader) we let it decide whether to act on
                mAccessibilityScriptInjected = false;
            // and consume the event.
            }
        } else if (mAccessibilityInjector != null) {
            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
                if (mAccessibilityInjector.onKeyEvent(event)) {
                    // if an accessibility injector is present (no JavaScript enabled or the site
                    // opts out injecting our JavaScript screen reader) we let it decide whether to
                    // act on and consume the event.
                    return true;
                    return true;
                }
                }
            } else {
                // Clean up if accessibility was disabled after loading the current URL.
                mAccessibilityInjector = null;
            }
        }


        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
+4 −4
Original line number Original line Diff line number Diff line
@@ -91,16 +91,16 @@
            0x16=0x04010100;
            0x16=0x04010100;
            <!-- Left Alt+DPAD/Trackball UP transitions from an axis to another and sends an event. -->
            <!-- Left Alt+DPAD/Trackball UP transitions from an axis to another and sends an event. -->
            <!-- Axis transitions:  2 -> 7; 1 -> 2; 0 -> 1; 3 -> 0; 4 -> 0; 5 -> 0; 6 -> 0; -->
            <!-- Axis transitions:  2 -> 7; 1 -> 2; 0 -> 1; 3 -> 0; 4 -> 0; 5 -> 0; 6 -> 0; -->
            0x120013=0x03020701:0x03010201:0x03000101:0x03030001:0x03040001:0x03050001:0x03060001;
            0x200000013=0x03020701:0x03010201:0x03000101:0x03030001:0x03040001:0x03050001:0x03060001;
            <!-- Left Alt+DPAD/Trackball DOWN transitions from an axis to another and sends an event. -->
            <!-- Left Alt+DPAD/Trackball DOWN transitions from an axis to another and sends an event. -->
            <!-- Axis transitions: 1 -> 0; 2 -> 1; 7 -> 2; 3 -> 7; 4 -> 7; 5 -> 7; 6 -> 7; -->
            <!-- Axis transitions: 1 -> 0; 2 -> 1; 7 -> 2; 3 -> 7; 4 -> 7; 5 -> 7; 6 -> 7; -->
            0x120014=0x03010001:0x03020101:0x03070201:0x03030701:0x03040701:0x03050701:0x03060701;
            0x200000014=0x03010001:0x03020101:0x03070201:0x03030701:0x03040701:0x03050701:0x03060701;
            <!-- Left Alt+DPAD/Trackball LEFT transitions from an axis to another and sends an event. -->
            <!-- Left Alt+DPAD/Trackball LEFT transitions from an axis to another and sends an event. -->
            <!-- Axis transitions: 4 -> 3; 5 -> 4; 6 -> 5; 0 -> 6; 1 -> 6; 2 -> 6; 7 -> 6; -->
            <!-- Axis transitions: 4 -> 3; 5 -> 4; 6 -> 5; 0 -> 6; 1 -> 6; 2 -> 6; 7 -> 6; -->
            0x120015=0x03040301:0x03050401:0x03060501:0x03000601:0x03010601:0x03020601:0x03070601;
            0x200000015=0x03040301:0x03050401:0x03060501:0x03000601:0x03010601:0x03020601:0x03070601;
            <!-- Left Alt+DPAD/Trackball RIGHT transitions from an axis to another and sends an event.  -->
            <!-- Left Alt+DPAD/Trackball RIGHT transitions from an axis to another and sends an event.  -->
            <!-- Axis transitions: 5 -> 6; 4 -> 5; 3 -> 4; 2 -> 3; 7 -> 3; 1 -> 3; 0 -> 3; -->
            <!-- Axis transitions: 5 -> 6; 4 -> 5; 3 -> 4; 2 -> 3; 7 -> 3; 1 -> 3; 0 -> 3; -->
            0x120016=0x03050601:0x03040501:0x03030401:0x03020301:0x03070301:0x03010301:0x03000301;
            0x200000016=0x03050601:0x03040501:0x03030401:0x03020301:0x03070301:0x03010301:0x03000301;
    </string>
    </string>


    <!-- Default for Settings.System.USER_ROTATION -->
    <!-- Default for Settings.System.USER_ROTATION -->