EventSenderImpl.java revision 8aff3c0571f078b0b212bd283278791ebc478da5
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.dumprendertree2; 18 19import android.os.Bundle; 20import android.os.Handler; 21import android.os.Message; 22import android.os.SystemClock; 23import android.util.Log; 24import android.view.KeyEvent; 25import android.view.MotionEvent; 26import android.webkit.WebView; 27 28import java.util.LinkedList; 29import java.util.List; 30 31/** 32 * An implementation of EventSender 33 */ 34public class EventSenderImpl { 35 private static final String LOG_TAG = "EventSenderImpl"; 36 37 private static final int MSG_ENABLE_DOM_UI_EVENT_LOGGING = 0; 38 private static final int MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT = 1; 39 private static final int MSG_LEAP_FORWARD = 2; 40 41 private static final int MSG_KEY_DOWN = 3; 42 43 private static final int MSG_MOUSE_DOWN = 4; 44 private static final int MSG_MOUSE_UP = 5; 45 private static final int MSG_MOUSE_CLICK = 6; 46 private static final int MSG_MOUSE_MOVE_TO = 7; 47 48 private static final int MSG_ADD_TOUCH_POINT = 8; 49 private static final int MSG_TOUCH_START = 9; 50 private static final int MSG_UPDATE_TOUCH_POINT = 10; 51 private static final int MSG_TOUCH_MOVE = 11; 52 private static final int MSG_CLEAR_TOUCH_POINTS = 12; 53 private static final int MSG_TOUCH_CANCEL = 13; 54 private static final int MSG_RELEASE_TOUCH_POINT = 14; 55 private static final int MSG_TOUCH_END = 15; 56 private static final int MSG_SET_TOUCH_MODIFIER = 16; 57 private static final int MSG_CANCEL_TOUCH_POINT = 17; 58 59 public static class TouchPoint { 60 WebView mWebView; 61 private int mX; 62 private int mY; 63 private long mDownTime; 64 private boolean mReleased = false; 65 private boolean mMoved = false; 66 private boolean mCancelled = false; 67 68 public TouchPoint(WebView webView, int x, int y) { 69 mWebView = webView; 70 mX = scaleX(x); 71 mY = scaleY(y); 72 } 73 74 public int getX() { 75 return mX; 76 } 77 78 public int getY() { 79 return mY; 80 } 81 82 public boolean hasMoved() { 83 return mMoved; 84 } 85 86 public void move(int newX, int newY) { 87 mX = scaleX(newX); 88 mY = scaleY(newY); 89 mMoved = true; 90 } 91 92 public void resetHasMoved() { 93 mMoved = false; 94 } 95 96 public long getDownTime() { 97 return mDownTime; 98 } 99 100 public void setDownTime(long downTime) { 101 mDownTime = downTime; 102 } 103 104 public boolean isReleased() { 105 return mReleased; 106 } 107 108 public void release() { 109 mReleased = true; 110 } 111 112 public boolean isCancelled() { 113 return mCancelled; 114 } 115 116 public void cancel() { 117 mCancelled = true; 118 } 119 120 private int scaleX(int x) { 121 return (int)(x * mWebView.getScale()) - mWebView.getScrollX(); 122 } 123 124 private int scaleY(int y) { 125 return (int)(y * mWebView.getScale()) - mWebView.getScrollY(); 126 } 127 } 128 129 private List<TouchPoint> mTouchPoints; 130 private int mTouchMetaState; 131 private int mMouseX; 132 private int mMouseY; 133 134 private WebView mWebView; 135 136 private Handler mEventSenderHandler = new Handler() { 137 @Override 138 public void handleMessage(Message msg) { 139 TouchPoint touchPoint; 140 Bundle bundle; 141 KeyEvent event; 142 143 switch (msg.what) { 144 case MSG_ENABLE_DOM_UI_EVENT_LOGGING: 145 /** TODO: implement */ 146 break; 147 148 case MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT: 149 /** TODO: implement */ 150 break; 151 152 case MSG_LEAP_FORWARD: 153 /** TODO: implement */ 154 break; 155 156 case MSG_KEY_DOWN: 157 bundle = (Bundle)msg.obj; 158 String character = bundle.getString("character"); 159 String[] withModifiers = bundle.getStringArray("withModifiers"); 160 161 if (withModifiers != null && withModifiers.length > 0) { 162 for (int i = 0; i < withModifiers.length; i++) { 163 executeKeyEvent(KeyEvent.ACTION_DOWN, 164 modifierToKeyCode(withModifiers[i])); 165 } 166 } 167 executeKeyEvent(KeyEvent.ACTION_DOWN, 168 charToKeyCode(character.toLowerCase().toCharArray()[0])); 169 break; 170 171 /** MOUSE */ 172 173 case MSG_MOUSE_DOWN: 174 /** TODO: Implement */ 175 break; 176 177 case MSG_MOUSE_UP: 178 /** TODO: Implement */ 179 break; 180 181 case MSG_MOUSE_CLICK: 182 /** TODO: Implement */ 183 break; 184 185 case MSG_MOUSE_MOVE_TO: 186 int x = msg.arg1; 187 int y = msg.arg2; 188 189 event = null; 190 if (x > mMouseX) { 191 event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT); 192 } else if (x < mMouseX) { 193 event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT); 194 } 195 if (event != null) { 196 mWebView.onKeyDown(event.getKeyCode(), event); 197 mWebView.onKeyUp(event.getKeyCode(), event); 198 } 199 200 event = null; 201 if (y > mMouseY) { 202 event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN); 203 } else if (y < mMouseY) { 204 event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP); 205 } 206 if (event != null) { 207 mWebView.onKeyDown(event.getKeyCode(), event); 208 mWebView.onKeyUp(event.getKeyCode(), event); 209 } 210 211 mMouseX = x; 212 mMouseY = y; 213 break; 214 215 /** TOUCH */ 216 217 case MSG_ADD_TOUCH_POINT: 218 getTouchPoints().add(new TouchPoint(mWebView, 219 msg.arg1, msg.arg2)); 220 if (getTouchPoints().size() > 1) { 221 Log.w(LOG_TAG + "::MSG_ADD_TOUCH_POINT", "Added more than one touch point"); 222 } 223 break; 224 225 case MSG_TOUCH_START: 226 /** 227 * FIXME: At the moment we don't support multi-touch. Hence, we only examine 228 * the first touch point. In future this method will need rewriting. 229 */ 230 if (getTouchPoints().isEmpty()) { 231 return; 232 } 233 touchPoint = getTouchPoints().get(0); 234 235 touchPoint.setDownTime(SystemClock.uptimeMillis()); 236 executeTouchEvent(touchPoint, MotionEvent.ACTION_DOWN); 237 break; 238 239 case MSG_UPDATE_TOUCH_POINT: 240 bundle = (Bundle)msg.obj; 241 242 int id = bundle.getInt("id"); 243 if (id >= getTouchPoints().size()) { 244 Log.w(LOG_TAG + "::MSG_UPDATE_TOUCH_POINT", "TouchPoint out of bounds: " 245 + id); 246 break; 247 } 248 249 getTouchPoints().get(id).move(bundle.getInt("x"), bundle.getInt("y")); 250 break; 251 252 case MSG_TOUCH_MOVE: 253 /** 254 * FIXME: At the moment we don't support multi-touch. Hence, we only examine 255 * the first touch point. In future this method will need rewriting. 256 */ 257 if (getTouchPoints().isEmpty()) { 258 return; 259 } 260 touchPoint = getTouchPoints().get(0); 261 262 if (!touchPoint.hasMoved()) { 263 return; 264 } 265 executeTouchEvent(touchPoint, MotionEvent.ACTION_MOVE); 266 touchPoint.resetHasMoved(); 267 break; 268 269 case MSG_CANCEL_TOUCH_POINT: 270 if (msg.arg1 >= getTouchPoints().size()) { 271 Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: " 272 + msg.arg1); 273 break; 274 } 275 276 getTouchPoints().get(msg.arg1).cancel(); 277 break; 278 279 case MSG_TOUCH_CANCEL: 280 /** 281 * FIXME: At the moment we don't support multi-touch. Hence, we only examine 282 * the first touch point. In future this method will need rewriting. 283 */ 284 if (getTouchPoints().isEmpty()) { 285 return; 286 } 287 touchPoint = getTouchPoints().get(0); 288 289 if (touchPoint.isCancelled()) { 290 executeTouchEvent(touchPoint, MotionEvent.ACTION_CANCEL); 291 } 292 break; 293 294 case MSG_RELEASE_TOUCH_POINT: 295 if (msg.arg1 >= getTouchPoints().size()) { 296 Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: " 297 + msg.arg1); 298 break; 299 } 300 301 getTouchPoints().get(msg.arg1).release(); 302 break; 303 304 case MSG_TOUCH_END: 305 /** 306 * FIXME: At the moment we don't support multi-touch. Hence, we only examine 307 * the first touch point. In future this method will need rewriting. 308 */ 309 if (getTouchPoints().isEmpty()) { 310 return; 311 } 312 touchPoint = getTouchPoints().get(0); 313 314 executeTouchEvent(touchPoint, MotionEvent.ACTION_UP); 315 if (touchPoint.isReleased()) { 316 getTouchPoints().remove(0); 317 touchPoint = null; 318 } 319 break; 320 321 case MSG_SET_TOUCH_MODIFIER: 322 bundle = (Bundle)msg.obj; 323 String modifier = bundle.getString("modifier"); 324 boolean enabled = bundle.getBoolean("enabled"); 325 326 int mask = 0; 327 if ("alt".equals(modifier.toLowerCase())) { 328 mask = KeyEvent.META_ALT_ON; 329 } else if ("shift".equals(modifier.toLowerCase())) { 330 mask = KeyEvent.META_SHIFT_ON; 331 } else if ("ctrl".equals(modifier.toLowerCase())) { 332 mask = KeyEvent.META_SYM_ON; 333 } 334 335 if (enabled) { 336 mTouchMetaState |= mask; 337 } else { 338 mTouchMetaState &= ~mask; 339 } 340 341 break; 342 343 case MSG_CLEAR_TOUCH_POINTS: 344 getTouchPoints().clear(); 345 break; 346 347 default: 348 break; 349 } 350 } 351 }; 352 353 public void reset(WebView webView) { 354 mWebView = webView; 355 mTouchPoints = null; 356 mTouchMetaState = 0; 357 mMouseX = 0; 358 mMouseY = 0; 359 } 360 361 public void enableDOMUIEventLogging(int domNode) { 362 Message msg = mEventSenderHandler.obtainMessage(MSG_ENABLE_DOM_UI_EVENT_LOGGING); 363 msg.arg1 = domNode; 364 msg.sendToTarget(); 365 } 366 367 public void fireKeyboardEventsToElement(int domNode) { 368 Message msg = mEventSenderHandler.obtainMessage(MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT); 369 msg.arg1 = domNode; 370 msg.sendToTarget(); 371 } 372 373 public void leapForward(int milliseconds) { 374 Message msg = mEventSenderHandler.obtainMessage(MSG_LEAP_FORWARD); 375 msg.arg1 = milliseconds; 376 msg.sendToTarget(); 377 } 378 379 public void keyDown(String character, String[] withModifiers) { 380 Bundle bundle = new Bundle(); 381 bundle.putString("character", character); 382 bundle.putStringArray("withModifiers", withModifiers); 383 mEventSenderHandler.obtainMessage(MSG_KEY_DOWN, bundle).sendToTarget(); 384 } 385 386 /** MOUSE */ 387 388 public void mouseDown() { 389 mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_DOWN); 390 } 391 392 public void mouseUp() { 393 mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_UP); 394 } 395 396 public void mouseClick() { 397 mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_CLICK); 398 } 399 400 public void mouseMoveTo(int x, int y) { 401 mEventSenderHandler.obtainMessage(MSG_MOUSE_MOVE_TO, x, y).sendToTarget(); 402 } 403 404 /** TOUCH */ 405 406 public void addTouchPoint(int x, int y) { 407 mEventSenderHandler.obtainMessage(MSG_ADD_TOUCH_POINT, x, y).sendToTarget(); 408 } 409 410 public void touchStart() { 411 mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_START); 412 } 413 414 public void updateTouchPoint(int id, int x, int y) { 415 Bundle bundle = new Bundle(); 416 bundle.putInt("id", id); 417 bundle.putInt("x", x); 418 bundle.putInt("y", y); 419 mEventSenderHandler.obtainMessage(MSG_UPDATE_TOUCH_POINT, bundle).sendToTarget(); 420 } 421 422 public void touchMove() { 423 mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_MOVE); 424 } 425 426 public void cancelTouchPoint(int id) { 427 Message msg = mEventSenderHandler.obtainMessage(MSG_CANCEL_TOUCH_POINT); 428 msg.arg1 = id; 429 msg.sendToTarget(); 430 } 431 432 public void touchCancel() { 433 mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_CANCEL); 434 } 435 436 public void releaseTouchPoint(int id) { 437 Message msg = mEventSenderHandler.obtainMessage(MSG_RELEASE_TOUCH_POINT); 438 msg.arg1 = id; 439 msg.sendToTarget(); 440 } 441 442 public void touchEnd() { 443 mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_END); 444 } 445 446 public void setTouchModifier(String modifier, boolean enabled) { 447 Bundle bundle = new Bundle(); 448 bundle.putString("modifier", modifier); 449 bundle.putBoolean("enabled", enabled); 450 mEventSenderHandler.obtainMessage(MSG_SET_TOUCH_MODIFIER, bundle).sendToTarget(); 451 } 452 453 public void clearTouchPoints() { 454 mEventSenderHandler.sendEmptyMessage(MSG_CLEAR_TOUCH_POINTS); 455 } 456 457 private List<TouchPoint> getTouchPoints() { 458 if (mTouchPoints == null) { 459 mTouchPoints = new LinkedList<TouchPoint>(); 460 } 461 462 return mTouchPoints; 463 } 464 465 private void executeTouchEvent(TouchPoint touchPoint, int action) { 466 MotionEvent event = 467 MotionEvent.obtain(touchPoint.getDownTime(), SystemClock.uptimeMillis(), 468 action, touchPoint.getX(), touchPoint.getY(), mTouchMetaState); 469 mWebView.onTouchEvent(event); 470 } 471 472 private void executeKeyEvent(int action, int keyCode) { 473 KeyEvent event = new KeyEvent(action, keyCode); 474 mWebView.onKeyDown(event.getKeyCode(), event); 475 } 476 477 /** 478 * Assumes lowercase chars, case needs to be handled by calling function. 479 */ 480 private static int charToKeyCode(char c) { 481 // handle numbers 482 if (c >= '0' && c <= '9') { 483 int offset = c - '0'; 484 return KeyEvent.KEYCODE_0 + offset; 485 } 486 487 // handle characters 488 if (c >= 'a' && c <= 'z') { 489 int offset = c - 'a'; 490 return KeyEvent.KEYCODE_A + offset; 491 } 492 493 // handle all others 494 switch (c) { 495 case '*': 496 return KeyEvent.KEYCODE_STAR; 497 498 case '#': 499 return KeyEvent.KEYCODE_POUND; 500 501 case ',': 502 return KeyEvent.KEYCODE_COMMA; 503 504 case '.': 505 return KeyEvent.KEYCODE_PERIOD; 506 507 case '\t': 508 return KeyEvent.KEYCODE_TAB; 509 510 case ' ': 511 return KeyEvent.KEYCODE_SPACE; 512 513 case '\n': 514 return KeyEvent.KEYCODE_ENTER; 515 516 case '\b': 517 case 0x7F: 518 return KeyEvent.KEYCODE_DEL; 519 520 case '~': 521 return KeyEvent.KEYCODE_GRAVE; 522 523 case '-': 524 return KeyEvent.KEYCODE_MINUS; 525 526 case '=': 527 return KeyEvent.KEYCODE_EQUALS; 528 529 case '(': 530 return KeyEvent.KEYCODE_LEFT_BRACKET; 531 532 case ')': 533 return KeyEvent.KEYCODE_RIGHT_BRACKET; 534 535 case '\\': 536 return KeyEvent.KEYCODE_BACKSLASH; 537 538 case ';': 539 return KeyEvent.KEYCODE_SEMICOLON; 540 541 case '\'': 542 return KeyEvent.KEYCODE_APOSTROPHE; 543 544 case '/': 545 return KeyEvent.KEYCODE_SLASH; 546 547 default: 548 return c; 549 } 550 } 551 552 private static int modifierToKeyCode(String modifier) { 553 if (modifier.equals("ctrlKey")) { 554 return KeyEvent.KEYCODE_ALT_LEFT; 555 } else if (modifier.equals("shiftKey")) { 556 return KeyEvent.KEYCODE_SHIFT_LEFT; 557 } else if (modifier.equals("altKey")) { 558 return KeyEvent.KEYCODE_SYM; 559 } 560 561 return KeyEvent.KEYCODE_UNKNOWN; 562 } 563}