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

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

Adding accessibility content provider responsible for user script injection....

Adding accessibility content provider responsible for user script injection. Adding support for injecting accessibility in WebViews with disabled JavaScript.

Change-Id: I1576238a0f855bb54e25d19404b8404d7d0f6037
parent a250f20b
Loading
Loading
Loading
Loading
+99 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.webkit;

import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.webkit.WebViewCore.EventHub;

/**
 * This class injects accessibility into WebViews with disabled JavaScript or
 * WebViews with enabled JavaScript but for which we have no accessibility
 * script to inject.
 */
class AccessibilityInjector {

    // Handle to the WebView this injector is associated with.
    private final WebView mWebView;

    /**
     * Creates a new injector associated with a given VwebView.
     *
     * @param webView The associated WebView.
     */
    public AccessibilityInjector(WebView webView) {
        mWebView = webView;
    }

    /**
     * Processes a key down <code>event</code>.
     *
     * @return True if the event was processed.
     */
    public boolean onKeyEvent(KeyEvent event) {

        // as a proof of concept let us do the simplest example

        if (event.getAction() != KeyEvent.ACTION_UP) {
            return false;
        }

        int keyCode = event.getKeyCode();

        switch (keyCode) {
            case KeyEvent.KEYCODE_N:
                modifySelection("extend", "forward", "sentence");
                break;
            case KeyEvent.KEYCODE_P:
                modifySelection("extend", "backward", "sentence");
                break;
        }

        return false;
    }

    /**
     * Called when the <code>selectionString</code> has changed.
     */
    public void onSelectionStringChange(String selectionString) {
        // put the selection string in an AccessibilityEvent and send it
        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
        event.getText().add(selectionString);
        mWebView.sendAccessibilityEventUnchecked(event);
    }

    /**
     * Modifies the current selection.
     *
     * @param alter Specifies how to alter the selection.
     * @param direction The direction in which to alter the selection.
     * @param granularity The granularity of the selection modification.
     */
    private void modifySelection(String alter, String direction, String granularity) {
        WebViewCore webViewCore = mWebView.getWebViewCore();

        if (webViewCore == null) {
            return;
        }

        WebViewCore.ModifySelectionData data = new WebViewCore.ModifySelectionData();
        data.mAlter = alter;
        data.mDirection = direction;
        data.mGranularity = granularity;
        webViewCore.sendMessage(EventHub.MODIFY_SELECTION, data);
    }
}
+86 −10
Original line number Diff line number Diff line
@@ -25,19 +25,13 @@ import android.content.DialogInterface.OnCancelListener;
import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Interpolator;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.http.SslCertificate;
@@ -46,9 +40,11 @@ import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.speech.tts.TextToSpeech;
import android.text.IClipboard;
import android.text.Selection;
import android.text.Spannable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
@@ -64,6 +60,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AlphaAnimation;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -89,7 +86,6 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
@@ -487,6 +483,10 @@ public class WebView extends AbsoluteLayout
    // use the framework's ScaleGestureDetector to handle multi-touch
    private ScaleGestureDetector mScaleDetector;

    // An instance for injecting accessibility in WebViews with disabled
    // JavaScript or ones for which no accessibility script exists
    private AccessibilityInjector mAccessibilityInjector;

    // the anchor point in the document space where VIEW_SIZE_CHANGED should
    // apply to
    private int mAnchorX;
@@ -545,6 +545,7 @@ public class WebView extends AbsoluteLayout
    static final int CENTER_FIT_RECT                    = 127;
    static final int REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID = 128;
    static final int SET_SCROLLBAR_MODES                = 129;
    static final int SELECTION_STRING_CHANGED           = 130;

    private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
    private static final int LAST_PACKAGE_MSG_ID = SET_SCROLLBAR_MODES;
@@ -670,6 +671,19 @@ public class WebView extends AbsoluteLayout
    private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
    private int mVerticalScrollBarMode = SCROLLBAR_AUTO;

    // the alias via which accessibility JavaScript interface is exposed
    private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";

    // JavaScript to inject the script chooser which will
    // pick the right script for the current URL
    private static final String ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT =
        "javascript:(function() {" +
        "    var chooser = document.createElement('script');" +
        "    chooser.type = 'text/javascript';" +
        "    chooser.src = 'https://ssl.gstatic.com/accessibility/javascript/android/AndroidScriptChooser.user.js';" +
        "    document.getElementsByTagName('head')[0].appendChild(chooser);" +
        "  })();";

    // Used to match key downs and key ups
    private boolean mGotKeyDown;

@@ -846,7 +860,7 @@ public class WebView extends AbsoluteLayout
     * @param context A Context object used to access application assets.
     * @param attrs An AttributeSet passed to our parent.
     * @param defStyle The default style resource ID.
     * @param javascriptInterfaces is a Map of intareface names, as keys, and
     * @param javascriptInterfaces is a Map of interface names, as keys, and
     * object implementing those interfaces, as values.
     * @hide pending API council approval.
     */
@@ -855,6 +869,13 @@ public class WebView extends AbsoluteLayout
        super(context, attrs, defStyle);
        init();

        if (AccessibilityManager.getInstance(context).isEnabled()) {
            if (javascriptInterfaces == null) {
                javascriptInterfaces = new HashMap<String, Object>();
            }
            exposeAccessibilityJavaScriptApi(javascriptInterfaces);
        }

        mCallbackProxy = new CallbackProxy(context, this);
        mViewManager = new ViewManager(this);
        mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
@@ -923,6 +944,26 @@ public class WebView extends AbsoluteLayout
        mMaximumFling = configuration.getScaledMaximumFlingVelocity();
    }

    /**
     * Exposes accessibility APIs to JavaScript by appending them to the JavaScript
     * interfaces map provided by the WebView client. In case of conflicting
     * alias with the one of the accessibility API the user specified one wins.
     *
     * @param javascriptInterfaces A map with interfaces to be exposed to JavaScript.
     */
    private void exposeAccessibilityJavaScriptApi(Map<String, Object> javascriptInterfaces) {
        if (javascriptInterfaces.containsKey(ALIAS_ACCESSIBILITY_JS_INTERFACE)) {
            Log.w(LOGTAG, "JavaScript interface mapped to \"" + ALIAS_ACCESSIBILITY_JS_INTERFACE
                    + "\" overrides the accessibility API JavaScript interface. No accessibility"
                    + "API will be exposed to JavaScript!");
            return;
        }

        // expose the TTS for now ...
        javascriptInterfaces.put(ALIAS_ACCESSIBILITY_JS_INTERFACE,
                new TextToSpeech(getContext(), null));
    }

    /* package */void updateDefaultZoomDensity(int zoomDensity) {
        final float density = getContext().getResources().getDisplayMetrics().density
                * 100 / zoomDensity;
@@ -2799,6 +2840,29 @@ public class WebView extends AbsoluteLayout
            }
            mPageThatNeedsToSlideTitleBarOffScreen = null;
        }

        injectAccessibilityForUrl(url);
    }

    /**
     * This method injects accessibility in the loaded document if accessibility
     * is enabled. If JavaScript is enabled we try to inject a URL specific script.
     * If no URL specific script is found or JavaScript is disabled we fallback to
     * the default {@link AccessibilityInjector} implementation.
     *
     * @param url The URL loaded by this {@link WebView}.
     */
    private void injectAccessibilityForUrl(String url) {
        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            if (getSettings().getJavaScriptEnabled()) {
                loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);
            } else if (mAccessibilityInjector == null) {
                mAccessibilityInjector = new AccessibilityInjector(this);
            }
        } else {
            // it is possible that accessibility was turned off between reloads
            mAccessibilityInjector = null;
        }
    }

    /**
@@ -3722,8 +3786,10 @@ public class WebView extends AbsoluteLayout
        // Bubble up the key event if
        // 1. it is a system key; or
        // 2. the host application wants to handle it;
        // 3. the accessibility injector is present and wants to handle it;
        if (event.isSystem()
                || mCallbackProxy.uiOverrideKeyEvent(event)) {
                || mCallbackProxy.uiOverrideKeyEvent(event)
                || (mAccessibilityInjector != null && mAccessibilityInjector.onKeyEvent(event))) {
            return false;
        }

@@ -3867,7 +3933,10 @@ public class WebView extends AbsoluteLayout
        // Bubble up the key event if
        // 1. it is a system key; or
        // 2. the host application wants to handle it;
        if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
        // 3. the accessibility injector is present and wants to handle it;
        if (event.isSystem()
                || mCallbackProxy.uiOverrideKeyEvent(event)
                || (mAccessibilityInjector != null && mAccessibilityInjector.onKeyEvent(event))) {
            return false;
        }

@@ -6721,6 +6790,13 @@ public class WebView extends AbsoluteLayout
                    mVerticalScrollBarMode = msg.arg2;
                    break;

                case SELECTION_STRING_CHANGED:
                    if (mAccessibilityInjector != null) {
                        String selectionString = (String) msg.obj;
                        mAccessibilityInjector.onSelectionStringChange(selectionString);
                    }
                    break;

                default:
                    super.handleMessage(msg);
                    break;
+33 −5
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package android.webkit;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.graphics.Canvas;
@@ -33,12 +32,10 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.provider.Browser;
import android.provider.OpenableColumns;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

@@ -578,6 +575,17 @@ final class WebViewCore {
     */
    private native void nativeProvideVisitedHistory(String[] history);

    /**
     * Modifies the current selection.
     *
     * @param alter Specifies how to alter the selection.
     * @param direction The direction in which to alter the selection.
     * @param granularity The granularity of the selection modification.
     *
     * @return The selection string.
     */
    private native String nativeModifySelection(String alter, String direction, String granularity);

    // EventHub for processing messages
    private final EventHub mEventHub;
    // WebCore thread handler
@@ -716,7 +724,11 @@ final class WebViewCore {
        boolean mRemember;
    }


    static class ModifySelectionData {
        String mAlter;
        String mDirection;
        String mGranularity;
    }

        static final String[] HandlerDebugString = {
            "REQUEST_LABEL", // 97
@@ -863,6 +875,9 @@ final class WebViewCore {
        static final int ADD_PACKAGE_NAME = 185;
        static final int REMOVE_PACKAGE_NAME = 186;

        // accessibility support
        static final int MODIFY_SELECTION = 190;

        // private message ids
        private static final int DESTROY =     200;

@@ -1236,6 +1251,19 @@ final class WebViewCore {
                            nativeSetSelection(msg.arg1, msg.arg2);
                            break;

                        case MODIFY_SELECTION:
                            ModifySelectionData modifySelectionData =
                                (ModifySelectionData) msg.obj;
                            String selectionString = nativeModifySelection(
                                    modifySelectionData.mAlter,
                                    modifySelectionData.mDirection,
                                    modifySelectionData.mGranularity);

                            mWebView.mPrivateHandler.obtainMessage(
                                    WebView.SELECTION_STRING_CHANGED, selectionString)
                                    .sendToTarget();
                            break;

                        case LISTBOX_CHOICES:
                            SparseBooleanArray choices = (SparseBooleanArray)
                                    msg.obj;