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