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}