• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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