From 8aff3c0571f078b0b212bd283278791ebc478da5 Mon Sep 17 00:00:00 2001 From: Maksymilian Osowski Date: Tue, 3 Aug 2010 17:42:47 +0100 Subject: Added eventSender. Change-Id: Iae31dc11ddd7b4b1b9c2e1c39fb61cc7b9832721 --- .../com/android/dumprendertree2/EventSender.java | 110 ++++ .../android/dumprendertree2/EventSenderImpl.java | 563 +++++++++++++++++++++ .../dumprendertree2/LayoutTestsExecutor.java | 10 +- 3 files changed, 680 insertions(+), 3 deletions(-) create mode 100644 tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java create mode 100644 tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java (limited to 'tests/DumpRenderTree2') diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java new file mode 100644 index 0000000..5b7cbc4 --- /dev/null +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java @@ -0,0 +1,110 @@ +/* + * 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 com.android.dumprendertree2; + +import android.webkit.WebView; + +/** + * A class that acts as a JS interface for webview to mock various touch events, + * mouse actions and key presses. + * + * The methods here just call corresponding methods on EventSenderImpl + * that contains the logic of how to execute the methods. + */ +public class EventSender { + EventSenderImpl mEventSenderImpl = new EventSenderImpl(); + + public void reset(WebView webView) { + mEventSenderImpl.reset(webView); + } + + public void enableDOMUIEventLogging(int domNode) { + mEventSenderImpl.enableDOMUIEventLogging(domNode); + } + + public void fireKeyboardEventsToElement(int domNode) { + mEventSenderImpl.fireKeyboardEventsToElement(domNode); + } + + public void keyDown(String character, String[] withModifiers) { + mEventSenderImpl.keyDown(character, withModifiers); + } + + public void keyDown(String character) { + keyDown(character, null); + } + + public void leapForward(int milliseconds) { + mEventSenderImpl.leapForward(milliseconds); + } + + public void mouseClick() { + mEventSenderImpl.mouseClick(); + } + + public void mouseDown() { + mEventSenderImpl.mouseDown(); + } + + public void mouseMoveTo(int x, int y) { + mEventSenderImpl.mouseMoveTo(x, y); + } + + public void mouseUp() { + mEventSenderImpl.mouseUp(); + } + + public void touchStart() { + mEventSenderImpl.touchStart(); + } + + public void addTouchPoint(int x, int y) { + mEventSenderImpl.addTouchPoint(x, y); + } + + public void updateTouchPoint(int id, int x, int y) { + mEventSenderImpl.updateTouchPoint(id, x, y); + } + + public void setTouchModifier(String modifier, boolean enabled) { + mEventSenderImpl.setTouchModifier(modifier, enabled); + } + + public void touchMove() { + mEventSenderImpl.touchMove(); + } + + public void releaseTouchPoint(int id) { + mEventSenderImpl.releaseTouchPoint(id); + } + + public void touchEnd() { + mEventSenderImpl.touchEnd(); + } + + public void touchCancel() { + mEventSenderImpl.touchCancel(); + } + + public void clearTouchPoints() { + mEventSenderImpl.clearTouchPoints(); + } + + public void cancelTouchPoint(int id) { + mEventSenderImpl.cancelTouchPoint(id); + } +} \ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java new file mode 100644 index 0000000..93e6137 --- /dev/null +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java @@ -0,0 +1,563 @@ +/* + * 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 com.android.dumprendertree2; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.webkit.WebView; + +import java.util.LinkedList; +import java.util.List; + +/** + * An implementation of EventSender + */ +public class EventSenderImpl { + private static final String LOG_TAG = "EventSenderImpl"; + + private static final int MSG_ENABLE_DOM_UI_EVENT_LOGGING = 0; + private static final int MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT = 1; + private static final int MSG_LEAP_FORWARD = 2; + + private static final int MSG_KEY_DOWN = 3; + + private static final int MSG_MOUSE_DOWN = 4; + private static final int MSG_MOUSE_UP = 5; + private static final int MSG_MOUSE_CLICK = 6; + private static final int MSG_MOUSE_MOVE_TO = 7; + + private static final int MSG_ADD_TOUCH_POINT = 8; + private static final int MSG_TOUCH_START = 9; + private static final int MSG_UPDATE_TOUCH_POINT = 10; + private static final int MSG_TOUCH_MOVE = 11; + private static final int MSG_CLEAR_TOUCH_POINTS = 12; + private static final int MSG_TOUCH_CANCEL = 13; + private static final int MSG_RELEASE_TOUCH_POINT = 14; + private static final int MSG_TOUCH_END = 15; + private static final int MSG_SET_TOUCH_MODIFIER = 16; + private static final int MSG_CANCEL_TOUCH_POINT = 17; + + public static class TouchPoint { + WebView mWebView; + private int mX; + private int mY; + private long mDownTime; + private boolean mReleased = false; + private boolean mMoved = false; + private boolean mCancelled = false; + + public TouchPoint(WebView webView, int x, int y) { + mWebView = webView; + mX = scaleX(x); + mY = scaleY(y); + } + + public int getX() { + return mX; + } + + public int getY() { + return mY; + } + + public boolean hasMoved() { + return mMoved; + } + + public void move(int newX, int newY) { + mX = scaleX(newX); + mY = scaleY(newY); + mMoved = true; + } + + public void resetHasMoved() { + mMoved = false; + } + + public long getDownTime() { + return mDownTime; + } + + public void setDownTime(long downTime) { + mDownTime = downTime; + } + + public boolean isReleased() { + return mReleased; + } + + public void release() { + mReleased = true; + } + + public boolean isCancelled() { + return mCancelled; + } + + public void cancel() { + mCancelled = true; + } + + private int scaleX(int x) { + return (int)(x * mWebView.getScale()) - mWebView.getScrollX(); + } + + private int scaleY(int y) { + return (int)(y * mWebView.getScale()) - mWebView.getScrollY(); + } + } + + private List mTouchPoints; + private int mTouchMetaState; + private int mMouseX; + private int mMouseY; + + private WebView mWebView; + + private Handler mEventSenderHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + TouchPoint touchPoint; + Bundle bundle; + KeyEvent event; + + switch (msg.what) { + case MSG_ENABLE_DOM_UI_EVENT_LOGGING: + /** TODO: implement */ + break; + + case MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT: + /** TODO: implement */ + break; + + case MSG_LEAP_FORWARD: + /** TODO: implement */ + break; + + case MSG_KEY_DOWN: + bundle = (Bundle)msg.obj; + String character = bundle.getString("character"); + String[] withModifiers = bundle.getStringArray("withModifiers"); + + if (withModifiers != null && withModifiers.length > 0) { + for (int i = 0; i < withModifiers.length; i++) { + executeKeyEvent(KeyEvent.ACTION_DOWN, + modifierToKeyCode(withModifiers[i])); + } + } + executeKeyEvent(KeyEvent.ACTION_DOWN, + charToKeyCode(character.toLowerCase().toCharArray()[0])); + break; + + /** MOUSE */ + + case MSG_MOUSE_DOWN: + /** TODO: Implement */ + break; + + case MSG_MOUSE_UP: + /** TODO: Implement */ + break; + + case MSG_MOUSE_CLICK: + /** TODO: Implement */ + break; + + case MSG_MOUSE_MOVE_TO: + int x = msg.arg1; + int y = msg.arg2; + + event = null; + if (x > mMouseX) { + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT); + } else if (x < mMouseX) { + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT); + } + if (event != null) { + mWebView.onKeyDown(event.getKeyCode(), event); + mWebView.onKeyUp(event.getKeyCode(), event); + } + + event = null; + if (y > mMouseY) { + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN); + } else if (y < mMouseY) { + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP); + } + if (event != null) { + mWebView.onKeyDown(event.getKeyCode(), event); + mWebView.onKeyUp(event.getKeyCode(), event); + } + + mMouseX = x; + mMouseY = y; + break; + + /** TOUCH */ + + case MSG_ADD_TOUCH_POINT: + getTouchPoints().add(new TouchPoint(mWebView, + msg.arg1, msg.arg2)); + if (getTouchPoints().size() > 1) { + Log.w(LOG_TAG + "::MSG_ADD_TOUCH_POINT", "Added more than one touch point"); + } + break; + + case MSG_TOUCH_START: + /** + * FIXME: At the moment we don't support multi-touch. Hence, we only examine + * the first touch point. In future this method will need rewriting. + */ + if (getTouchPoints().isEmpty()) { + return; + } + touchPoint = getTouchPoints().get(0); + + touchPoint.setDownTime(SystemClock.uptimeMillis()); + executeTouchEvent(touchPoint, MotionEvent.ACTION_DOWN); + break; + + case MSG_UPDATE_TOUCH_POINT: + bundle = (Bundle)msg.obj; + + int id = bundle.getInt("id"); + if (id >= getTouchPoints().size()) { + Log.w(LOG_TAG + "::MSG_UPDATE_TOUCH_POINT", "TouchPoint out of bounds: " + + id); + break; + } + + getTouchPoints().get(id).move(bundle.getInt("x"), bundle.getInt("y")); + break; + + case MSG_TOUCH_MOVE: + /** + * FIXME: At the moment we don't support multi-touch. Hence, we only examine + * the first touch point. In future this method will need rewriting. + */ + if (getTouchPoints().isEmpty()) { + return; + } + touchPoint = getTouchPoints().get(0); + + if (!touchPoint.hasMoved()) { + return; + } + executeTouchEvent(touchPoint, MotionEvent.ACTION_MOVE); + touchPoint.resetHasMoved(); + break; + + case MSG_CANCEL_TOUCH_POINT: + if (msg.arg1 >= getTouchPoints().size()) { + Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: " + + msg.arg1); + break; + } + + getTouchPoints().get(msg.arg1).cancel(); + break; + + case MSG_TOUCH_CANCEL: + /** + * FIXME: At the moment we don't support multi-touch. Hence, we only examine + * the first touch point. In future this method will need rewriting. + */ + if (getTouchPoints().isEmpty()) { + return; + } + touchPoint = getTouchPoints().get(0); + + if (touchPoint.isCancelled()) { + executeTouchEvent(touchPoint, MotionEvent.ACTION_CANCEL); + } + break; + + case MSG_RELEASE_TOUCH_POINT: + if (msg.arg1 >= getTouchPoints().size()) { + Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: " + + msg.arg1); + break; + } + + getTouchPoints().get(msg.arg1).release(); + break; + + case MSG_TOUCH_END: + /** + * FIXME: At the moment we don't support multi-touch. Hence, we only examine + * the first touch point. In future this method will need rewriting. + */ + if (getTouchPoints().isEmpty()) { + return; + } + touchPoint = getTouchPoints().get(0); + + executeTouchEvent(touchPoint, MotionEvent.ACTION_UP); + if (touchPoint.isReleased()) { + getTouchPoints().remove(0); + touchPoint = null; + } + break; + + case MSG_SET_TOUCH_MODIFIER: + bundle = (Bundle)msg.obj; + String modifier = bundle.getString("modifier"); + boolean enabled = bundle.getBoolean("enabled"); + + int mask = 0; + if ("alt".equals(modifier.toLowerCase())) { + mask = KeyEvent.META_ALT_ON; + } else if ("shift".equals(modifier.toLowerCase())) { + mask = KeyEvent.META_SHIFT_ON; + } else if ("ctrl".equals(modifier.toLowerCase())) { + mask = KeyEvent.META_SYM_ON; + } + + if (enabled) { + mTouchMetaState |= mask; + } else { + mTouchMetaState &= ~mask; + } + + break; + + case MSG_CLEAR_TOUCH_POINTS: + getTouchPoints().clear(); + break; + + default: + break; + } + } + }; + + public void reset(WebView webView) { + mWebView = webView; + mTouchPoints = null; + mTouchMetaState = 0; + mMouseX = 0; + mMouseY = 0; + } + + public void enableDOMUIEventLogging(int domNode) { + Message msg = mEventSenderHandler.obtainMessage(MSG_ENABLE_DOM_UI_EVENT_LOGGING); + msg.arg1 = domNode; + msg.sendToTarget(); + } + + public void fireKeyboardEventsToElement(int domNode) { + Message msg = mEventSenderHandler.obtainMessage(MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT); + msg.arg1 = domNode; + msg.sendToTarget(); + } + + public void leapForward(int milliseconds) { + Message msg = mEventSenderHandler.obtainMessage(MSG_LEAP_FORWARD); + msg.arg1 = milliseconds; + msg.sendToTarget(); + } + + public void keyDown(String character, String[] withModifiers) { + Bundle bundle = new Bundle(); + bundle.putString("character", character); + bundle.putStringArray("withModifiers", withModifiers); + mEventSenderHandler.obtainMessage(MSG_KEY_DOWN, bundle).sendToTarget(); + } + + /** MOUSE */ + + public void mouseDown() { + mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_DOWN); + } + + public void mouseUp() { + mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_UP); + } + + public void mouseClick() { + mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_CLICK); + } + + public void mouseMoveTo(int x, int y) { + mEventSenderHandler.obtainMessage(MSG_MOUSE_MOVE_TO, x, y).sendToTarget(); + } + + /** TOUCH */ + + public void addTouchPoint(int x, int y) { + mEventSenderHandler.obtainMessage(MSG_ADD_TOUCH_POINT, x, y).sendToTarget(); + } + + public void touchStart() { + mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_START); + } + + public void updateTouchPoint(int id, int x, int y) { + Bundle bundle = new Bundle(); + bundle.putInt("id", id); + bundle.putInt("x", x); + bundle.putInt("y", y); + mEventSenderHandler.obtainMessage(MSG_UPDATE_TOUCH_POINT, bundle).sendToTarget(); + } + + public void touchMove() { + mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_MOVE); + } + + public void cancelTouchPoint(int id) { + Message msg = mEventSenderHandler.obtainMessage(MSG_CANCEL_TOUCH_POINT); + msg.arg1 = id; + msg.sendToTarget(); + } + + public void touchCancel() { + mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_CANCEL); + } + + public void releaseTouchPoint(int id) { + Message msg = mEventSenderHandler.obtainMessage(MSG_RELEASE_TOUCH_POINT); + msg.arg1 = id; + msg.sendToTarget(); + } + + public void touchEnd() { + mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_END); + } + + public void setTouchModifier(String modifier, boolean enabled) { + Bundle bundle = new Bundle(); + bundle.putString("modifier", modifier); + bundle.putBoolean("enabled", enabled); + mEventSenderHandler.obtainMessage(MSG_SET_TOUCH_MODIFIER, bundle).sendToTarget(); + } + + public void clearTouchPoints() { + mEventSenderHandler.sendEmptyMessage(MSG_CLEAR_TOUCH_POINTS); + } + + private List getTouchPoints() { + if (mTouchPoints == null) { + mTouchPoints = new LinkedList(); + } + + return mTouchPoints; + } + + private void executeTouchEvent(TouchPoint touchPoint, int action) { + MotionEvent event = + MotionEvent.obtain(touchPoint.getDownTime(), SystemClock.uptimeMillis(), + action, touchPoint.getX(), touchPoint.getY(), mTouchMetaState); + mWebView.onTouchEvent(event); + } + + private void executeKeyEvent(int action, int keyCode) { + KeyEvent event = new KeyEvent(action, keyCode); + mWebView.onKeyDown(event.getKeyCode(), event); + } + + /** + * Assumes lowercase chars, case needs to be handled by calling function. + */ + private static int charToKeyCode(char c) { + // handle numbers + if (c >= '0' && c <= '9') { + int offset = c - '0'; + return KeyEvent.KEYCODE_0 + offset; + } + + // handle characters + if (c >= 'a' && c <= 'z') { + int offset = c - 'a'; + return KeyEvent.KEYCODE_A + offset; + } + + // handle all others + switch (c) { + case '*': + return KeyEvent.KEYCODE_STAR; + + case '#': + return KeyEvent.KEYCODE_POUND; + + case ',': + return KeyEvent.KEYCODE_COMMA; + + case '.': + return KeyEvent.KEYCODE_PERIOD; + + case '\t': + return KeyEvent.KEYCODE_TAB; + + case ' ': + return KeyEvent.KEYCODE_SPACE; + + case '\n': + return KeyEvent.KEYCODE_ENTER; + + case '\b': + case 0x7F: + return KeyEvent.KEYCODE_DEL; + + case '~': + return KeyEvent.KEYCODE_GRAVE; + + case '-': + return KeyEvent.KEYCODE_MINUS; + + case '=': + return KeyEvent.KEYCODE_EQUALS; + + case '(': + return KeyEvent.KEYCODE_LEFT_BRACKET; + + case ')': + return KeyEvent.KEYCODE_RIGHT_BRACKET; + + case '\\': + return KeyEvent.KEYCODE_BACKSLASH; + + case ';': + return KeyEvent.KEYCODE_SEMICOLON; + + case '\'': + return KeyEvent.KEYCODE_APOSTROPHE; + + case '/': + return KeyEvent.KEYCODE_SLASH; + + default: + return c; + } + } + + private static int modifierToKeyCode(String modifier) { + if (modifier.equals("ctrlKey")) { + return KeyEvent.KEYCODE_ALT_LEFT; + } else if (modifier.equals("shiftKey")) { + return KeyEvent.KEYCODE_SHIFT_LEFT; + } else if (modifier.equals("altKey")) { + return KeyEvent.KEYCODE_SYM; + } + + return KeyEvent.KEYCODE_UNKNOWN; + } +} \ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java index dc82c34..047348d 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java @@ -110,6 +110,8 @@ public class LayoutTestsExecutor extends Activity { private boolean mSetGeolocationPermissionCalled; private boolean mGeolocationPermission; + private EventSender mEventSender = new EventSender(); + private WakeLock mScreenDimLock; /** COMMUNICATION WITH ManagerService */ @@ -248,7 +250,7 @@ public class LayoutTestsExecutor extends Activity { mCurrentTestIndex = intent.getIntExtra(EXTRA_TEST_INDEX, -1); mTotalTestCount = mCurrentTestIndex + mTestsList.size(); - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); mScreenDimLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "WakeLock in LayoutTester"); mScreenDimLock.acquire(); @@ -269,6 +271,8 @@ public class LayoutTestsExecutor extends Activity { mCurrentWebView = new WebView(this); setupWebView(mCurrentWebView); + mEventSender.reset(mCurrentWebView); + setContentView(mCurrentWebView); if (previousWebView != null) { Log.d(LOG_TAG + "::reset", "previousWebView != null"); @@ -280,6 +284,7 @@ public class LayoutTestsExecutor extends Activity { webView.setWebViewClient(mWebViewClient); webView.setWebChromeClient(mWebChromeClient); webView.addJavascriptInterface(mLayoutTestController, "layoutTestController"); + webView.addJavascriptInterface(mEventSender, "eventSender"); /** * Setting a touch interval of -1 effectively disables the optimisation in WebView @@ -462,8 +467,7 @@ public class LayoutTestsExecutor extends Activity { Handler mLayoutTestControllerHandler = new Handler() { @Override public void handleMessage(Message msg) { - assert mCurrentState.isRunningState() - : "mCurrentState = " + mCurrentState.name(); + assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name(); switch (msg.what) { case MSG_WAIT_UNTIL_DONE: -- cgit v1.1