Merge "Handle WebKit selection changes synchronously."
This commit is contained in:
committed by
Android (Google) Code Review
commit
c163c606f6
@@ -318,12 +318,15 @@ class AccessibilityInjector {
|
||||
/**
|
||||
* Attempts to handle selection change events when accessibility is using a
|
||||
* non-JavaScript method.
|
||||
* <p>
|
||||
* This must not be called from the main thread.
|
||||
*
|
||||
* @param selectionString The selection string.
|
||||
* @param selection The selection string.
|
||||
* @param token The selection request token.
|
||||
*/
|
||||
public void handleSelectionChangedIfNecessary(String selectionString) {
|
||||
public void onSelectionStringChangedWebCoreThread(String selection, int token) {
|
||||
if (mAccessibilityInjectorFallback != null) {
|
||||
mAccessibilityInjectorFallback.onSelectionStringChange(selectionString);
|
||||
mAccessibilityInjectorFallback.onSelectionStringChangedWebCoreThread(selection, token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,9 @@ import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.webkit.WebViewCore.EventHub;
|
||||
|
||||
import com.android.internal.os.SomeArgs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* This class injects accessibility into WebViews with disabled JavaScript or
|
||||
@@ -48,8 +49,7 @@ import java.util.Stack;
|
||||
* </p>
|
||||
* The possible actions are invocations to
|
||||
* {@link #setCurrentAxis(int, boolean, String)}, or
|
||||
* {@link #traverseCurrentAxis(int, boolean, String)}
|
||||
* {@link #traverseGivenAxis(int, int, boolean, String)}
|
||||
* {@link #traverseGivenAxis(int, int, boolean, String, boolean)}
|
||||
* {@link #performAxisTransition(int, int, boolean, String)}
|
||||
* referred via the values of:
|
||||
* {@link #ACTION_SET_CURRENT_AXIS},
|
||||
@@ -74,6 +74,9 @@ class AccessibilityInjectorFallback {
|
||||
private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
|
||||
private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
|
||||
|
||||
/** Timeout after which asynchronous granular movement is aborted. */
|
||||
private static final int MODIFY_SELECTION_TIMEOUT = 500;
|
||||
|
||||
// WebView navigation axes from WebViewCore.h, plus an additional axis for
|
||||
// the default behavior.
|
||||
private static final int NAVIGATION_AXIS_CHARACTER = 0;
|
||||
@@ -81,7 +84,8 @@ class AccessibilityInjectorFallback {
|
||||
private static final int NAVIGATION_AXIS_SENTENCE = 2;
|
||||
@SuppressWarnings("unused")
|
||||
private static final int NAVIGATION_AXIS_HEADING = 3;
|
||||
private static final int NAVIGATION_AXIS_SIBLING = 5;
|
||||
@SuppressWarnings("unused")
|
||||
private static final int NAVIGATION_AXIS_SIBLING = 4;
|
||||
@SuppressWarnings("unused")
|
||||
private static final int NAVIGATION_AXIS_PARENT_FIRST_CHILD = 5;
|
||||
private static final int NAVIGATION_AXIS_DOCUMENT = 6;
|
||||
@@ -99,8 +103,11 @@ class AccessibilityInjectorFallback {
|
||||
private final WebViewClassic mWebView;
|
||||
private final WebView mWebViewInternal;
|
||||
|
||||
// events scheduled for sending as soon as we receive the selected text
|
||||
private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
|
||||
// Event scheduled for sending as soon as we receive the selected text.
|
||||
private AccessibilityEvent mScheduledEvent;
|
||||
|
||||
// Token required to send the scheduled event.
|
||||
private int mScheduledToken = 0;
|
||||
|
||||
// the current traversal axis
|
||||
private int mCurrentAxis = 2; // sentence
|
||||
@@ -114,6 +121,15 @@ class AccessibilityInjectorFallback {
|
||||
// keep track of last direction
|
||||
private int mLastDirection;
|
||||
|
||||
// Lock used for asynchronous selection callback.
|
||||
private final Object mCallbackLock = new Object();
|
||||
|
||||
// Whether the asynchronous selection callback was received.
|
||||
private boolean mCallbackReceived;
|
||||
|
||||
// Whether the asynchronous selection callback succeeded.
|
||||
private boolean mCallbackResult;
|
||||
|
||||
/**
|
||||
* Creates a new injector associated with a given {@link WebViewClassic}.
|
||||
*
|
||||
@@ -174,8 +190,8 @@ class AccessibilityInjectorFallback {
|
||||
}
|
||||
mLastDirection = direction;
|
||||
sendEvent = (binding.getSecondArgument(i) == 1);
|
||||
mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
|
||||
contentDescription);
|
||||
mLastDownEventHandled = traverseGivenAxis(
|
||||
direction, mCurrentAxis, sendEvent, contentDescription, false);
|
||||
break;
|
||||
case ACTION_TRAVERSE_GIVEN_AXIS:
|
||||
direction = binding.getFirstArgument(i);
|
||||
@@ -187,7 +203,7 @@ class AccessibilityInjectorFallback {
|
||||
mLastDirection = direction;
|
||||
axis = binding.getSecondArgument(i);
|
||||
sendEvent = (binding.getThirdArgument(i) == 1);
|
||||
traverseGivenAxis(direction, axis, sendEvent, contentDescription);
|
||||
traverseGivenAxis(direction, axis, sendEvent, contentDescription, false);
|
||||
mLastDownEventHandled = true;
|
||||
break;
|
||||
case ACTION_PERFORM_AXIS_TRANSITION:
|
||||
@@ -207,7 +223,7 @@ class AccessibilityInjectorFallback {
|
||||
mLastDirection = binding.getFirstArgument(i);
|
||||
sendEvent = (binding.getSecondArgument(i) == 1);
|
||||
traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
|
||||
sendEvent, contentDescription);
|
||||
sendEvent, contentDescription, false);
|
||||
mLastDownEventHandled = false;
|
||||
} else {
|
||||
mLastDownEventHandled = true;
|
||||
@@ -222,8 +238,7 @@ class AccessibilityInjectorFallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current navigation axis which will be used while
|
||||
* calling {@link #traverseCurrentAxis(int, boolean, String)}.
|
||||
* Set the current navigation axis.
|
||||
*
|
||||
* @param axis The axis to set.
|
||||
* @param sendEvent Whether to send an accessibility event to
|
||||
@@ -255,20 +270,6 @@ class AccessibilityInjectorFallback {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the document along the current navigation axis.
|
||||
*
|
||||
* @param direction The direction of traversal.
|
||||
* @param sendEvent Whether to send an accessibility event to
|
||||
* announce the change.
|
||||
* @param contentDescription A description of the performed action.
|
||||
* @see #setCurrentAxis(int, boolean, String)
|
||||
*/
|
||||
private boolean traverseCurrentAxis(int direction, boolean sendEvent,
|
||||
String contentDescription) {
|
||||
return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
|
||||
}
|
||||
|
||||
boolean performAccessibilityAction(int action, Bundle arguments) {
|
||||
switch (action) {
|
||||
case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
|
||||
@@ -276,14 +277,14 @@ class AccessibilityInjectorFallback {
|
||||
final int direction = getDirectionForAction(action);
|
||||
final int axis = getAxisForGranularity(arguments.getInt(
|
||||
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
|
||||
return traverseGivenAxis(direction, axis, true, null);
|
||||
return traverseGivenAxis(direction, axis, true, null, true);
|
||||
}
|
||||
case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
|
||||
case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: {
|
||||
final int direction = getDirectionForAction(action);
|
||||
// TODO: Add support for moving by object.
|
||||
final int axis = NAVIGATION_AXIS_SENTENCE;
|
||||
return traverseGivenAxis(direction, axis, true, null);
|
||||
return traverseGivenAxis(direction, axis, true, null, true);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
@@ -293,7 +294,7 @@ class AccessibilityInjectorFallback {
|
||||
/**
|
||||
* Returns the {@link WebView}-defined direction for the given
|
||||
* {@link AccessibilityNodeInfo}-defined action.
|
||||
*
|
||||
*
|
||||
* @param action An accessibility action identifier.
|
||||
* @return A web view navigation direction.
|
||||
*/
|
||||
@@ -313,7 +314,7 @@ class AccessibilityInjectorFallback {
|
||||
/**
|
||||
* Returns the {@link WebView}-defined axis for the given
|
||||
* {@link AccessibilityNodeInfo}-defined granularity.
|
||||
*
|
||||
*
|
||||
* @param granularity An accessibility granularity identifier.
|
||||
* @return A web view navigation axis.
|
||||
*/
|
||||
@@ -345,20 +346,20 @@ class AccessibilityInjectorFallback {
|
||||
* @param contentDescription A description of the performed action.
|
||||
*/
|
||||
private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
|
||||
String contentDescription) {
|
||||
WebViewCore webViewCore = mWebView.getWebViewCore();
|
||||
String contentDescription, boolean sychronous) {
|
||||
final WebViewCore webViewCore = mWebView.getWebViewCore();
|
||||
if (webViewCore == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessibilityEvent event = null;
|
||||
if (sendEvent) {
|
||||
event = getPartialyPopulatedAccessibilityEvent(
|
||||
final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(
|
||||
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
|
||||
// the text will be set upon receiving the selection string
|
||||
// The text will be set upon receiving the selection string.
|
||||
event.setContentDescription(contentDescription);
|
||||
mScheduledEvent = event;
|
||||
mScheduledToken++;
|
||||
}
|
||||
mScheduledEventStack.push(event);
|
||||
|
||||
// if the axis is the default let WebView handle the event which will
|
||||
// result in cursor ring movement and selection of its content
|
||||
@@ -366,27 +367,78 @@ class AccessibilityInjectorFallback {
|
||||
return false;
|
||||
}
|
||||
|
||||
webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
|
||||
return true;
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.argi1 = direction;
|
||||
args.argi2 = axis;
|
||||
args.argi3 = mScheduledToken;
|
||||
|
||||
// If we don't need synchronous results, just return true.
|
||||
if (!sychronous) {
|
||||
webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
|
||||
return true;
|
||||
}
|
||||
|
||||
final boolean callbackResult;
|
||||
|
||||
synchronized (mCallbackLock) {
|
||||
mCallbackReceived = false;
|
||||
|
||||
// Asynchronously changes the selection in WebView, which responds by
|
||||
// calling onSelectionStringChanged().
|
||||
webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
|
||||
|
||||
try {
|
||||
mCallbackLock.wait(MODIFY_SELECTION_TIMEOUT);
|
||||
} catch (InterruptedException e) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
callbackResult = mCallbackResult;
|
||||
}
|
||||
|
||||
return (mCallbackReceived && callbackResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the <code>selectionString</code> has changed.
|
||||
*/
|
||||
public void onSelectionStringChange(String selectionString) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "Selection string: " + selectionString);
|
||||
/* package */ void onSelectionStringChangedWebCoreThread(
|
||||
final String selection, final int token) {
|
||||
synchronized (mCallbackLock) {
|
||||
mCallbackReceived = true;
|
||||
mCallbackResult = (selection != null);
|
||||
mCallbackLock.notifyAll();
|
||||
}
|
||||
mIsLastSelectionStringNull = (selectionString == null);
|
||||
if (mScheduledEventStack.isEmpty()) {
|
||||
|
||||
// Managing state and sending events must take place on the UI thread.
|
||||
mWebViewInternal.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onSelectionStringChangedMainThread(selection, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onSelectionStringChangedMainThread(String selection, int token) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "Selection string: " + selection);
|
||||
}
|
||||
|
||||
if (token != mScheduledToken) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "Selection string has incorrect token: " + token);
|
||||
}
|
||||
return;
|
||||
}
|
||||
AccessibilityEvent event = mScheduledEventStack.pop();
|
||||
if ((event != null) && (selectionString != null)) {
|
||||
event.getText().add(selectionString);
|
||||
|
||||
mIsLastSelectionStringNull = (selection == null);
|
||||
|
||||
final AccessibilityEvent event = mScheduledEvent;
|
||||
mScheduledEvent = null;
|
||||
|
||||
if ((event != null) && (selection != null)) {
|
||||
event.getText().add(selection);
|
||||
event.setFromIndex(0);
|
||||
event.setToIndex(selectionString.length());
|
||||
event.setToIndex(selection.length());
|
||||
sendAccessibilityEvent(event);
|
||||
event.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1024,30 +1024,26 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
static final int UPDATE_MATCH_COUNT = 126;
|
||||
static final int CENTER_FIT_RECT = 127;
|
||||
static final int SET_SCROLLBAR_MODES = 129;
|
||||
static final int SELECTION_STRING_CHANGED = 130;
|
||||
static final int HIT_TEST_RESULT = 131;
|
||||
static final int SAVE_WEBARCHIVE_FINISHED = 132;
|
||||
|
||||
static final int SET_AUTOFILLABLE = 133;
|
||||
static final int AUTOFILL_COMPLETE = 134;
|
||||
|
||||
static final int SCREEN_ON = 136;
|
||||
static final int UPDATE_ZOOM_DENSITY = 139;
|
||||
static final int EXIT_FULLSCREEN_VIDEO = 140;
|
||||
|
||||
static final int COPY_TO_CLIPBOARD = 141;
|
||||
static final int INIT_EDIT_FIELD = 142;
|
||||
static final int REPLACE_TEXT = 143;
|
||||
static final int CLEAR_CARET_HANDLE = 144;
|
||||
static final int KEY_PRESS = 145;
|
||||
static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
|
||||
static final int FOCUS_NODE_CHANGED = 147;
|
||||
static final int AUTOFILL_FORM = 148;
|
||||
static final int SCROLL_EDIT_TEXT = 149;
|
||||
static final int EDIT_TEXT_SIZE_CHANGED = 150;
|
||||
static final int SHOW_CARET_HANDLE = 151;
|
||||
static final int UPDATE_CONTENT_BOUNDS = 152;
|
||||
static final int SCROLL_HANDLE_INTO_VIEW = 153;
|
||||
static final int HIT_TEST_RESULT = 130;
|
||||
static final int SAVE_WEBARCHIVE_FINISHED = 131;
|
||||
static final int SET_AUTOFILLABLE = 132;
|
||||
static final int AUTOFILL_COMPLETE = 133;
|
||||
static final int SCREEN_ON = 134;
|
||||
static final int UPDATE_ZOOM_DENSITY = 135;
|
||||
static final int EXIT_FULLSCREEN_VIDEO = 136;
|
||||
static final int COPY_TO_CLIPBOARD = 137;
|
||||
static final int INIT_EDIT_FIELD = 138;
|
||||
static final int REPLACE_TEXT = 139;
|
||||
static final int CLEAR_CARET_HANDLE = 140;
|
||||
static final int KEY_PRESS = 141;
|
||||
static final int RELOCATE_AUTO_COMPLETE_POPUP = 142;
|
||||
static final int FOCUS_NODE_CHANGED = 143;
|
||||
static final int AUTOFILL_FORM = 144;
|
||||
static final int SCROLL_EDIT_TEXT = 145;
|
||||
static final int EDIT_TEXT_SIZE_CHANGED = 146;
|
||||
static final int SHOW_CARET_HANDLE = 147;
|
||||
static final int UPDATE_CONTENT_BOUNDS = 148;
|
||||
static final int SCROLL_HANDLE_INTO_VIEW = 149;
|
||||
|
||||
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
|
||||
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
|
||||
@@ -1766,6 +1762,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
|
||||
}
|
||||
|
||||
/* package */ void handleSelectionChangedWebCoreThread(String selection, int token) {
|
||||
if (isAccessibilityInjectionEnabled()) {
|
||||
getAccessibilityInjector().onSelectionStringChangedWebCoreThread(selection, token);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAccessibilityInjectionEnabled() {
|
||||
final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
|
||||
if (!manager.isEnabled()) {
|
||||
@@ -7496,13 +7498,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
mVerticalScrollBarMode = msg.arg2;
|
||||
break;
|
||||
|
||||
case SELECTION_STRING_CHANGED:
|
||||
if (isAccessibilityInjectionEnabled()) {
|
||||
getAccessibilityInjector()
|
||||
.handleSelectionChangedIfNecessary((String) msg.obj);
|
||||
}
|
||||
break;
|
||||
|
||||
case FOCUS_NODE_CHANGED:
|
||||
mIsEditingText = (msg.arg1 == mFieldPointer);
|
||||
if (mAutoCompletePopup != null && !mIsEditingText) {
|
||||
|
||||
@@ -41,6 +41,8 @@ import android.view.View;
|
||||
import android.webkit.WebViewClassic.FocusNodeHref;
|
||||
import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
|
||||
|
||||
import com.android.internal.os.SomeArgs;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import java.io.OutputStream;
|
||||
@@ -1545,12 +1547,14 @@ public final class WebViewCore {
|
||||
case MODIFY_SELECTION:
|
||||
mTextSelectionChangeReason
|
||||
= TextSelectionData.REASON_ACCESSIBILITY_INJECTOR;
|
||||
String modifiedSelectionString =
|
||||
nativeModifySelection(mNativeClass, msg.arg1,
|
||||
msg.arg2);
|
||||
mWebViewClassic.mPrivateHandler.obtainMessage(
|
||||
WebViewClassic.SELECTION_STRING_CHANGED,
|
||||
modifiedSelectionString).sendToTarget();
|
||||
final SomeArgs args = (SomeArgs) msg.obj;
|
||||
final String modifiedSelectionString = nativeModifySelection(
|
||||
mNativeClass, args.argi1, args.argi2);
|
||||
// If accessibility is on, the main thread may be
|
||||
// waiting for a response. Send on webcore thread.
|
||||
mWebViewClassic.handleSelectionChangedWebCoreThread(
|
||||
modifiedSelectionString, args.argi3);
|
||||
args.recycle();
|
||||
mTextSelectionChangeReason
|
||||
= TextSelectionData.REASON_UNKNOWN;
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user