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}