Home | History | Annotate | Download | only in dumprendertree2
      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 
     63         public Point(int x, int y) {
     64             mX = x;
     65             mY = y;
     66         }
     67         public int x() {
     68             return mX;
     69         }
     70         public int y() {
     71             return mY;
     72         }
     73     }
     74 
     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 
     88         public TouchPoint(int id, Point point) {
     89             mId = id;
     90             mPoint = point;
     91         }
     92 
     93         public int getId() {
     94           return mId;
     95         }
     96 
     97         public int getX() {
     98             return mPoint.x();
     99         }
    100 
    101         public int getY() {
    102             return mPoint.y();
    103         }
    104 
    105         public boolean hasMoved() {
    106             return mMoved;
    107         }
    108 
    109         public void move(Point point) {
    110             mPoint = point;
    111             mMoved = true;
    112         }
    113 
    114         public void resetHasMoved() {
    115             mMoved = false;
    116         }
    117 
    118         public long getDownTime() {
    119             return mDownTime;
    120         }
    121 
    122         public void setDownTime(long downTime) {
    123             mDownTime = downTime;
    124         }
    125 
    126         public boolean isReleased() {
    127             return mReleased;
    128         }
    129 
    130         public void release() {
    131             mReleased = true;
    132         }
    133 
    134         public boolean isCancelled() {
    135             return mCancelled;
    136         }
    137 
    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 
    343     public void reset(WebView webView) {
    344         mWebView = webView;
    345         mTouchPoints = null;
    346         mTouchMetaState = 0;
    347         mMousePoint = null;
    348     }
    349 
    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 
    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 
    362     public void leapForward(int milliseconds) {
    363         Message msg = mEventSenderHandler.obtainMessage(MSG_LEAP_FORWARD);
    364         msg.arg1 = milliseconds;
    365         msg.sendToTarget();
    366     }
    367 
    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 
    377     public void mouseDown() {
    378         mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_DOWN);
    379     }
    380 
    381     public void mouseUp() {
    382         mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_UP);
    383     }
    384 
    385     public void mouseClick() {
    386         mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_CLICK);
    387     }
    388 
    389     public void mouseMoveTo(int x, int y) {
    390         mEventSenderHandler.obtainMessage(MSG_MOUSE_MOVE_TO, x, y).sendToTarget();
    391     }
    392 
    393     /** TOUCH */
    394 
    395     public void addTouchPoint(int x, int y) {
    396         mEventSenderHandler.obtainMessage(MSG_ADD_TOUCH_POINT, x, y).sendToTarget();
    397     }
    398 
    399     public void touchStart() {
    400         mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_START);
    401     }
    402 
    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 
    411     public void touchMove() {
    412         mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_MOVE);
    413     }
    414 
    415     public void cancelTouchPoint(int id) {
    416         Message msg = mEventSenderHandler.obtainMessage(MSG_CANCEL_TOUCH_POINT);
    417         msg.arg1 = id;
    418         msg.sendToTarget();
    419     }
    420 
    421     public void touchCancel() {
    422         mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_CANCEL);
    423     }
    424 
    425     public void releaseTouchPoint(int id) {
    426         Message msg = mEventSenderHandler.obtainMessage(MSG_RELEASE_TOUCH_POINT);
    427         msg.arg1 = id;
    428         msg.sendToTarget();
    429     }
    430 
    431     public void touchEnd() {
    432         mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_END);
    433     }
    434 
    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 
    442     public void clearTouchPoints() {
    443         mEventSenderHandler.sendEmptyMessage(MSG_CLEAR_TOUCH_POINTS);
    444     }
    445 
    446     private List<TouchPoint> getTouchPoints() {
    447         if (mTouchPoints == null) {
    448             mTouchPoints = new LinkedList<TouchPoint>();
    449         }
    450 
    451         return mTouchPoints;
    452     }
    453 
    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 
    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      */
    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 
    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