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